D3.js Sunburst Incorrect Arc Scales - javascript

I have a Zoomable Sunburst diagram exhibiting strange problems with arc sizing.
http://colinwhite.net/Sunburst/
I would expect the size the arcs to be proportional to number of children (shown in the tool tip). Yet, I have parent arcs with few children, that are proportionally larger than their peers with far more children. Arc size is not reflective of number of children. I've tried various other d3.scales which haven't helped. What am I doing wrong?
My code is largely boiler plate from the D3 examples.
var width = 760, height = 700,
radius = Math.min(width, height) / 2.25,
color = d3.scale.category20c();
var x = d3.scale.linear().range([0, 2 * Math.PI]),
y = d3.scale.sqrt().range([0, radius]);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height * .52 + ")");
var partition = d3.layout.partition()
.value(function(d) { return 1; });
var arc = d3.svg.arc()
.startAngle(function(d) { return Math.max(0, Math.min( 2 * Math.PI, x(d.x))); })
.endAngle(function(d) { return Math.max(0, Math.min( 2 * Math.PI, x(d.x + d.x))); })
.innerRadius(function(d) { return Math.max(0, y(d.y)); })
.outerRadius(function(d) { return Math.max(0, y(d.y + d.dy)); });
var tooltip = d3.select("body")
.append("div")
.attr("class", "tooltip")
.style("position", "absolute")
.style("z-index", "10")
.style("opacity", 0);
d3.json("data/getJson.php", function(error, data) {
var treeData = genJSON(data, ['Location', 'Provider', 'Diagnosis', 'Procedure']);
console.log(treeData);
var path = svg.selectAll("path")
.data(partition.nodes(treeData))
.enter().append("svg:path")
.attr("d", arc)
.style("fill-rule", "evenodd")
.style("fill", function(d) { return color((d.children ? d : d.parent).name); })
.on("click", click)
.on("mouseover", function(d) {
tooltip.html(function() {
return (d.children ? d : d.parent).name + " (" + d.value + ")";
});
return tooltip.transition()
.duration(50)
.style("opacity", 0.9);
})
.on("mousemove", function(d) {
return tooltip
.style("top", (d3.event.pageY-10)+"px")
.style("left", (d3.event.pageX+10)+"px");
})
.on("mouseout", function(){return tooltip.style("opacity", 0);});
function click(d) {
path.transition()
.duration(750)
.attrTween("d", arcTween(d));
}
});
function arcTween(d) {
var xd = d3.interpolate(x.domain(), [d.x, d.x + d.dx]),
yd = d3.interpolate(y.domain(), [d.y, 1]),
yr = d3.interpolate(y.range(), [d.y ? 20 : 0, radius]);
return function(d,i) {
return i
? function(t) { return arc(d); }
: function(t) { x.domain(xd(t)); y.domain(yd(t)).range(yr(t)); return arc(d); };
};
}
The JSON is nested with this genJSON function -http://colinwhite.net/Sunburst/js/treeRemapper.js
Thanks for any help or advice.

Changing the partition call, to include a .sort(null) like this -
var partition = d3.layout.partition()
.sort(null) //<-- was missing this
.value(function(d) { return 1; });
Seems to have resolved the strange arc scale problems.

Related

How can I alter the zooming capability on a d3 sunburst chart?

I am trying to alter the traditional zooming feature on a sunburst chart. Traditionally when you click on a partition, that partition grows to cover 100% of the base layer while all other partitions on the same layer disappear. The children of the selected partition all grow to fill the newly created space.
My current code does just what I stated above. I would like to alter my code to allow for the selected partition to only take up 75% of the base layer. The children elements will grow to cover this new space but the remaining 25% will still contain all other non-selected partitions.
I have tried altering the 't' value that is returned from d3.interpolate() but I have had unpredictable results.
I hope my description is clear.
Does anyone have any thoughts on this?
<script>
var width = 960,
height = 700,
radius = Math.min(width, height) / 2;
var x = d3.scale.linear()
.range([0, 2 * Math.PI]);
var y = d3.scale.linear()
.range([0, radius]);
var color = d3.scale.category20c();
function percent(d) {
var percentage = (d.value / 956129) * 100;
return percentage.toFixed(2);
}
var tip = d3.tip()
.attr('class', 'd3-tip')
.offset([-10, 0])
.html(function(d) {
return "<strong>" + d.name + "</strong> <span style='color:red'>" + percent(d) + "%</span>";
})
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + (height / 2 + 10) + ")");
svg.call(tip);
var partition = d3.layout.partition()
.value(function(d) { return d.size; });
var arc = d3.svg.arc()
.startAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x))); })
.endAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx))); })
.innerRadius(function(d) { return Math.max(0, y(d.y)) })
.outerRadius(function(d) { return Math.max(0, y(d.y + d.dy)) });
d3.json("flare.json", function(error, root) {
var g = svg.selectAll("g")
.data(partition.nodes(root))
.enter().append("g");
var path = g.append("path")
.attr("d", arc)
// .attr("stroke", 'black')
// .style("fill", function(d) { return color((d.children ? d : d.parent).name); })
.style("fill", function(d, i) {
return color(i);
})
.on("click", click)
.on('mouseover', tip.show)
.on('mouseout', tip.hide);
var text = g.append("text")
.attr("transform", function(d) { return "rotate(" + computeTextRotation(d) + ")"; })
.attr("x", function(d) { return y(d.y); })
.attr("dx", "6") // margin
.attr("dy", ".35em") // vertical-align
.text(function(d) {
if (percent(d) > 1.35) {
return d.name;
}
})
.attr('font-size', function(d) {
if (d.value < 100000) {
return '10px'
} else {
return '20px';
}
})
.on("click", click)
.on('mouseover', tip.show)
.on('mouseout', tip.hide);
function click(d) {
console.log(d)
// fade out all text elements
text.transition().attr("opacity", 0);
path
.transition()
.duration(750)
.attrTween("d", arcTween(d))
.each("end", function(e, i) {
// check if the animated element's data e lies within the visible angle span given in d
if (e.x >= d.x && e.x < (d.x + d.dx)) {
// get a selection of the associated text element
var arcText = d3.select(this.parentNode).select("text");
// fade in the text element and recalculate positions
arcText.transition().duration(750)
.attr("opacity", 1)
.attr("transform", function() { return "rotate(" + computeTextRotation(e) + ")" })
.attr("x", function(d) { return y(d.y); });
}
});
}
});
d3.select(self.frameElement).style("height", height + "px");
// Interpolate the scales!
function arcTween(d) {
console.log(d.name, x.domain())
console.log(d.name, y.domain())
console.log(d.name, y.range())
var xd = d3.interpolate(x.domain(), [d.x, d.x + d.dx]),
yd = d3.interpolate(y.domain(), [d.y, 1]),
yr = d3.interpolate(y.range(), [d.y ? 20 : 0, radius]);
return function(d, i) {
return i
? function(t) { return arc(d); }
: function(t) {
console.log(t)
x.domain(xd(t));
y.domain(yd(t)).range(yr(t));
return arc(d);
};
};
}
function computeTextRotation(d) {
return (x(d.x + d.dx / 2) - Math.PI / 2) / Math.PI * 180;
}
I found the solution here: https://bl.ocks.org/mbostock/1306365. This example manages the zoom without getting rid of the sibling nodes.

D3 V4 Sunburst diagram layout arc calculation

This is the code to compute the coordinates of the sunburst nodes:
var arc = d3.svg.arc()
.startAngle(function(d) { return d.x; })
.endAngle(function(d) { return d.x + d.dx; })
.innerRadius(function(d) { return Math.sqrt(d.y); })
.outerRadius(function(d) { return Math.sqrt(d.y + d.dy); });
Where:
x: the minimum x-coordinate of the node position
y: the minimum y-coordinate of the node position
dx: the x-extent of the node position
dy: the y-extent of the node position
However, in the recently realeased version v4, the space-filling layouts d3.treemap and d3.partition now output x0, x1, y0, y1 on each node instead of x0, dx, y0, dy
node.x0 - the left edge of the rectangle
node.y0 - the top edge of the rectangle
node.x1 - the right edge of the rectangle
node.y1 - the bottom edge of the rectangle
What would be the correspinding code for v4 as the following does not produce the correct layout?
var arc = d3.arc()
.startAngle(function(d) { return d.x0; })
.endAngle(function(d) { return d.x0 + (d.x1 - d.x0); })
.innerRadius(function(d) { return d.y0; })
.outerRadius(function(d) { return d.y0 + (d.y1 - d.y0); });
See codepen
See codepen
var data = {...}
var width = 960;
var height = 700;
var radius = Math.min(width, height) / 2;
var color = d3.scaleOrdinal(d3.schemeCategory20c)
var g = d3.select('#container')
.append('svg')
.attr('width', width)
.attr('height', height)
.append('g')
.attr('transform', 'translate(' + width/2 + ',' + height/2 + ')');
var partition = d3.partition()
.size([360, radius])
.padding(0)
//.round(true); //this will produce weird leaf node size if true
var root = d3.hierarchy(data, function(d) { return d.children })
.sum( function(d) {
if(d.children) {
return 0
} else {
return 1
}
})
.sort(null);
partition(root)
var xScale = d3.scaleLinear()
.domain([0, radius])
.range([0, Math.PI * 2])
.clamp(true);
var arc = d3.arc()
.startAngle(function(d) { return xScale(d.x0) })
.endAngle(function(d) { return xScale(d.x1) })
.innerRadius(function(d) { return d.y0 })
.outerRadius(function(d) { return d.y1 })
var path = g.selectAll('path')
.data(root.descendants())
.enter().append('path')
.attr("display", function(d) { return d.depth ? null : "none"; })
.attr("d", arc)
.attr("fill-rule", "evenodd")
.style('stroke', '#fff')
.style("fill", function(d) { return color((d.children ? d : d.parent).data.name); })

D3 Partition - Show next level when clicked

I have a D3 partition which shows all the levels for the entire partition.
I would like to only show the first level when the chart loads and then show subsequent levels on click.
For example in this Tree the next level is shown on click of a node: D3Tree
Here is the code for my partition: Plunker link
$(document).ready(function(){
var width = 600,
height = 400,
radius = (Math.min(width, height) / 2) - 10;
var formatNumber = d3.format(",d");
var x = d3.scale.linear()
.range([0, 2 * Math.PI]);
var y = d3.scale.sqrt()
.range([0, radius]);
var color = d3.scale.category20c();
var partition = d3.layout.partition()
.value(function(d) {
if(d.depth == 2)
console.log(d.depth, d);
return 1; // d.size;
});
var arc = d3.svg.arc()
.startAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x))); })
.endAngle(function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x + d.dx))); })
.innerRadius(function(d) { return Math.max(0, y(d.y)); })
.outerRadius(function(d) { return Math.max(0, y(d.y + d.dy)); });
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + (height / 2) + ")");
d3.json("flare.json", function(error, root) {
if (error) throw error;
svg.selectAll("path")
.data(partition.nodes(root))
.enter().append("path")
.attr("d", arc)
.style("fill", function(d) { return color((d.children ? d : d.parent).name); })
.on("click", click)
.append("title")
.text(function(d) { return d.name + "\n" + formatNumber(d.value); });
});
function click(d) {
svg.transition()
.duration(750)
.tween("scale", function() {
var xd = d3.interpolate(x.domain(), [d.x, d.x + d.dx]),
yd = d3.interpolate(y.domain(), [d.y, 1]),
yr = d3.interpolate(y.range(), [d.y ? 20 : 0, radius]);
return function(t) { x.domain(xd(t)); y.domain(yd(t)).range(yr(t)); };
})
.selectAll("path")
.attrTween("d", function(d) { return function() { return arc(d); }; });
}
d3.select(self.frameElement).style("height", height + "px");
});
I would like to do something like toggle on click:
// Toggle children.
function toggle(d) {
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
}
Where the children get set and unset, then redrawn
To do something like the tree layout would be a little tough doing it with the help of display is a a cake walk.
When the path are drawn for the first time make all the nodes whose depth > 1 disappear using display:none:
svg.selectAll("path")
.data(partition.nodes(root))
.enter().append("path")
.attr("d", arc)
.style("fill", function(d) {
return color((d.children ? d : d.parent).name);
})
.style("display", function(d) {
if (d.depth > 1) {
return "none";//nodes whose depth is more than 1 make its vanish
} else {
return "";
}
})
Now on node click make all nodes reappear except when root node is clicked.
.style("display", function(d1) {
if (d.depth == 0 && d1.depth > 1) {
return "none"//root node clicked so show only 2 depths.
} else {
return "";
}
})
Working code here

Piece out effect for Multilayer pie Chart using d3.js

I have a multilayer pie chart with different inner radius as shown below:
The code for the above pie chart one can find here:
var width = 960,
height = 500,
radius = Math.min(width, height) / 2;
var color = d3.scale.ordinal()
.range(["cyan", "green", "blue", "brown", "violet", "orange", "purple"]);
var arcMajor = d3.svg.arc()
.outerRadius(function (d) {
return radius - 20;
})
.innerRadius(0);
//this for making the minor arc
var arcMinor = d3.svg.arc()
.outerRadius(function (d) {
// scale for calculating the radius range([20, radius - 40])
var s = scale((d.data.major - d.data.minor));
if (s > radius - 20) {
return radius - 20;
}
return scale(d.data.major - d.data.minor);
})
.innerRadius(0);
var labelr = 260;
var pie = d3.layout.pie()
.sort(null)
.value(function (d) {
return d.major;
});
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
data = [{
major: 500,
minor: 250,
grp: 1
}, {
major: 100,
minor: 80,
grp: 2
}, {
major: 100,
minor: 50,
grp: 3
}, {
major: 100,
minor: 60,
grp: 4
}, {
major: 100,
minor: 10,
grp: 5
}];
var scale = d3.scale.linear()
.range([d3.min(data, function (d) {
return d.minor;
}), radius - 100 - d3.max(data, function (d) {
return d.minor / d.major;
})])
//setting the scale domain
.domain([d3.min(data, function (d) {
return d.minor;
}), d3.max(data, function (d) {
return d.minor;
})]);
var g = svg.selectAll(".arc")
.data(pie(data))
.enter().append("g")
.attr("class", "arc");
g.append("svg:text")
.attr("transform", function (d) {
var c = arcMajor.centroid(d),
x = c[0],
y = c[1],
// pythagorean theorem for hypotenuse
h = Math.sqrt(x * x + y * y);
return "translate(" + (x / h * labelr) + ',' +
(y / h * labelr) + ")";
})
.attr("dy", ".35em")
.attr("text-anchor", function (d) {
// are we past the center?
return (d.endAngle + d.startAngle) / 2 > Math.PI ?
"end" : "start";
})
.text(function (d, i) { return d.value.toFixed(2); });
//this makes the major arc
g.append("path")
.attr("d", function (d) {
return arcMajor(d);
})
.style("fill", function (d) {
return d3.rgb(color(d.data.grp));
});
//this makes the minor arcs
g.append("path")
.attr("d", function (d) {
return arcMinor(d);
})
.style("fill", function (d) {
return d3.rgb(color(d.data.grp)).darker(2);//for making the inner path darker
});
http://jsfiddle.net/6e8aue0h/10/
I want to add piece out feature to this pie. Like this:
I tried using d3-pie plugin but it didnt work.
When you hover over particular section it should get pie out as shown in below figure.
https://github.com/dansdom/plugins-d3-pie
How can I implement this in this particular situation?
Thanking you very much.
here I added piece out effect for major pie in similar fashion you are able to add for inner pie. I have added arc over variable
var arcOver = d3.svg.arc()
.outerRadius(radius + 9);
and mouseenter and mouseout function for major arc.
.on("mouseenter", function(d) {
d3.select(this)
.attr("stroke","white")
.transition()
.duration(1000)
.attr("d", arcOver)
.attr("stroke-width",6);
})
.on("mouseleave", function(d) {
d3.select(this).transition()
.attr("d", arcMajor)
.attr("stroke","none");
})
here is the sample.
var width = 960,
height = 500,
radius = Math.min(width, height) / 2;
var color = d3.scale.ordinal()
.range(["cyan", "green", "blue", "brown", "violet", "orange", "purple"]);
var arcMajor = d3.svg.arc()
.outerRadius(function (d) {
return radius - 20;
})
.innerRadius(0);
var arcOver = d3.svg.arc()
.outerRadius(radius + 9);
//this for making the minor arc
var arcMinor = d3.svg.arc()
.outerRadius(function (d) {
// scale for calculating the radius range([20, radius - 40])
var s = scale((d.data.major - d.data.minor));
if (s > radius - 20) {
return radius - 20;
}
return scale(d.data.major - d.data.minor);
})
.innerRadius(0);
var arcOverMin = d3.svg.arc()
.outerRadius(radius - 90 );
// Define the div for the tooltip
var div = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
var labelr = 260;
var pie = d3.layout.pie()
.sort(null)
.value(function (d) {
return d.major;
});
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
data = [{
major: 500,
minor: 250,
grp: 1
}, {
major: 100,
minor: 80,
grp: 2
}, {
major: 100,
minor: 50,
grp: 3
}, {
major: 100,
minor: 60,
grp: 4
}, {
major: 100,
minor: 10,
grp: 5
}];
var scale = d3.scale.linear()
.range([d3.min(data, function (d) {
return d.minor;
}), radius - 100 - d3.max(data, function (d) {
return d.minor / d.major;
})])
//setting the scale domain
.domain([d3.min(data, function (d) {
return d.minor;
}), d3.max(data, function (d) {
return d.minor;
})]);
var g = svg.selectAll(".arc")
.data(pie(data))
.enter().append("g")
.attr("class", "arc");
g.append("svg:text")
.attr("transform", function (d) {
var c = arcMajor.centroid(d),
x = c[0],
y = c[1],
// pythagorean theorem for hypotenuse
h = Math.sqrt(x * x + y * y);
return "translate(" + (x / h * labelr) + ',' +
(y / h * labelr) + ")";
})
.attr("dy", ".35em")
.attr("text-anchor", function (d) {
// are we past the center?
return (d.endAngle + d.startAngle) / 2 > Math.PI ?
"end" : "start";
})
.text(function (d, i) { return d.value.toFixed(2); });
//this makes the major arc
g.append("path")
.attr("d", function (d) {
return arcMajor(d);
})
.on("mouseenter", function(d) {
d3.select(this)
.attr("stroke","white")
.transition()
.duration(1000)
.attr("d", arcOver)
.attr("stroke-width",6);
div.transition()
.duration(200)
.style("opacity", .9);
div.html(
'<a href= "http://facebook.com">' + // The first <a> tag
d.data.major +
"</a>"
+ "<br/>" + d.data.minor)
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 28) + "px");
})
.on("mouseleave", function(d) {
d3.select(this).transition()
.attr("d", arcMajor)
.attr("stroke","none");
})
.style("fill", function (d) {
return d3.rgb(color(d.data.grp));
});
//this makes the minor arcs
g.append("path")
.attr("d", function (d) {
return arcMinor(d);
})
.on("mouseenter", function(d) {
d3.select(this)
.attr("stroke","white")
.transition()
.duration(1000)
.attr("d", arcOverMin)
.attr("stroke-width",6);
})
.on("mouseleave", function(d) {
d3.select(this).transition()
.attr("d", arcMinor)
.attr("stroke","none");
})
.style("fill", function (d) {
return d3.rgb(color(d.data.grp)).darker(2);//for making the inner path darker
});
.arc text {
font: 10px sans-serif;
text-anchor: middle;
}
.arc path {
stroke: #fff;
}
div.tooltip {
position: absolute;
text-align: center;
width: 60px;
height: 28px;
padding: 2px;
font: 12px sans-serif;
background: lightsteelblue;
border: 0px;
border-radius: 8px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
You will just need to increase the outerRadius of arcs for implementing this effect.
Hope this helps.
var arcMajorOver = d3.svg.arc()
.outerRadius(function(d) {
return radius - 10;
});
var arcMinorOver = d3.svg.arc()
.outerRadius(function(d) {
var s = scale((d.data.major - d.data.minor));
if (s > radius - 20) {
return radius - 20;
}
return scale(d.data.major - d.data.minor) + 10;
});
//this makes the major arc
g.append("path")
.attr("d", function(d) {
return arcMajor(d);
})
.style("fill", function(d) {
return d3.rgb(color(d.data.grp));
}).on("mouseenter", function(d) {
d3.select(this)
.attr("stroke", "white")
.transition()
.duration(1000)
.attr("d", arcMajorOver)
.style("stroke-width",6);
})
.on("mouseleave", function(d) {
d3.select(this).transition()
.attr("d", arcMajor)
.style("stroke-width",0);
});;;
//this makes the minor arcs
g.append("path")
.attr("d", function(d) {
return arcMinor(d);
})
.style("fill", function(d) {
return d3.rgb(color(d.data.grp)).darker(2); //for making the inner path darker
}).on("mouseenter", function(d) {
d3.select(this)
.attr("stroke", "white")
.transition()
.duration(1000)
.attr("d", arcMinorOver)
.style("stroke-width",6);
})
.on("mouseleave", function(d) {
d3.select(this).transition()
.attr("d", arcMinor)
.style("stroke-width",0);
});
var width = 960,
height = 500,
radius = Math.min(width, height) / 2;
var color = d3.scale.ordinal()
.range(["cyan", "green", "blue", "brown", "violet", "orange", "purple"]);
var arcMajor = d3.svg.arc()
.outerRadius(function(d) {
return radius - 20;
})
.innerRadius(0);
var arcMajorOver = d3.svg.arc()
.outerRadius(function(d) {
return radius - 10;
});
//this for making the minor arc
var arcMinor = d3.svg.arc()
.outerRadius(function(d) {
// scale for calculating the radius range([20, radius - 40])
var s = scale((d.data.major - d.data.minor));
if (s > radius - 20) {
return radius - 20;
}
return scale(d.data.major - d.data.minor);
})
.innerRadius(0);
var arcMinorOver = d3.svg.arc()
.outerRadius(function(d) {
var s = scale((d.data.major - d.data.minor));
if (s > radius - 20) {
return radius - 20;
}
return scale(d.data.major - d.data.minor) + 10;
});
var labelr = 260;
var pie = d3.layout.pie()
.sort(null)
.value(function(d) {
return d.major;
});
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
data = [{
major: 500,
minor: 250,
grp: 1
}, {
major: 100,
minor: 80,
grp: 2
}, {
major: 100,
minor: 50,
grp: 3
}, {
major: 100,
minor: 60,
grp: 4
}, {
major: 100,
minor: 10,
grp: 5
}];
var scale = d3.scale.linear()
.range([d3.min(data, function(d) {
return d.minor;
}), radius - 100 - d3.max(data, function(d) {
return d.minor / d.major;
})])
//setting the scale domain
.domain([d3.min(data, function(d) {
return d.minor;
}), d3.max(data, function(d) {
return d.minor;
})]);
var g = svg.selectAll(".arc")
.data(pie(data))
.enter().append("g")
.attr("class", "arc");
g.append("svg:text")
.attr("transform", function(d) {
var c = arcMajor.centroid(d),
x = c[0],
y = c[1],
// pythagorean theorem for hypotenuse
h = Math.sqrt(x * x + y * y);
return "translate(" + (x / h * labelr) + ',' +
(y / h * labelr) + ")";
})
.attr("dy", ".35em")
.attr("text-anchor", function(d) {
// are we past the center?
return (d.endAngle + d.startAngle) / 2 > Math.PI ?
"end" : "start";
})
.text(function(d, i) {
return d.value.toFixed(2);
});
//this makes the major arc
g.append("path")
.attr("d", function(d) {
return arcMajor(d);
})
.style("fill", function(d) {
return d3.rgb(color(d.data.grp));
}).on("mouseenter", function(d) {
d3.select(this)
.transition()
.duration(1000)
.attr("d", arcMajorOver)
.style("stroke-width",6);
})
.on("mouseleave", function(d) {
d3.select(this).transition()
.attr("d", arcMajor)
.style("stroke-width", 0);
});;;
//this makes the minor arcs
g.append("path")
.attr("d", function(d) {
return arcMinor(d);
})
.style("fill", function(d) {
return d3.rgb(color(d.data.grp)).darker(2); //for making the inner path darker
}).on("mouseenter", function(d) {
d3.select(this)
.transition()
.duration(1000)
.attr("d", arcMinorOver)
.style("stroke-width",6);
})
.on("mouseleave", function(d) {
d3.select(this).transition()
.attr("d", arcMinor)
.style("stroke-width", 0);
});
.arc text {
font: 10px sans-serif;
text-anchor: middle;
}
.arc path {
stroke: #fff;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>

Alternating or preventing overlapping paths in D3

I am creating an arc diagram where I'd like to, hopefully, find a way to prevent the overlap of arcs. There's an example of the working bl.ock here.
The darker lines in this case are overlapping lines where multiple nodes share the same edge. I'd like to prevent that, perhaps by doing two passes: the first would alternate the arc to go above the nodes rather than below, giving a sort of helix appearance; the second would draw a slightly larger arc if an arc already exists above/below to help differentiate the links.
var width = 1000,
height = 500,
margin = 20,
pad = margin / 2,
radius = 6,
yfixed = pad + radius;
var color = d3.scale.category10();
// Main
//-----------------------------------------------------
function arcDiagram(graph) {
var radius = d3.scale.sqrt()
.domain([0, 20])
.range([0, 15]);
var svg = d3.select("#chart").append("svg")
.attr("id", "arc")
.attr("width", width)
.attr("height", height);
// create plot within svg
var plot = svg.append("g")
.attr("id", "plot")
.attr("transform", "translate(" + pad + ", " + pad + ")");
// fix graph links to map to objects
graph.links.forEach(function(d,i) {
d.source = isNaN(d.source) ? d.source : graph.nodes[d.source];
d.target = isNaN(d.target) ? d.target : graph.nodes[d.target];
});
linearLayout(graph.nodes);
drawLinks(graph.links);
drawNodes(graph.nodes);
}
// layout nodes linearly
function linearLayout(nodes) {
nodes.sort(function(a,b) {
return a.uniq - b.uniq;
})
var xscale = d3.scale.linear()
.domain([0, nodes.length - 1])
.range([radius, width - margin - radius]);
nodes.forEach(function(d, i) {
d.x = xscale(i);
d.y = yfixed;
});
}
function drawNodes(nodes) {
var gnodes = d3.select("#plot").selectAll("g.node")
.data(nodes)
.enter().append('g');
var nodes = gnodes.append("circle")
.attr("class", "node")
.attr("id", function(d, i) { return d.name; })
.attr("cx", function(d, i) { return d.x; })
.attr("cy", function(d, i) { return d.y; })
.attr("r", 5)
.style("stroke", function(d, i) { return color(d.gender); });
nodes.append("text")
.attr("dx", function(d) { return 20; })
.attr("cy", ".35em")
.text(function(d) { return d.name; })
}
function drawLinks(links) {
var radians = d3.scale.linear()
.range([Math.PI / 2, 3 * Math.PI / 2]);
var arc = d3.svg.line.radial()
.interpolate("basis")
.tension(0)
.angle(function(d) { return radians(d); });
d3.select("#plot").selectAll(".link")
.data(links)
.enter().append("path")
.attr("class", "link")
.attr("transform", function(d,i) {
var xshift = d.source.x + (d.target.x - d.source.x) / 2;
var yshift = yfixed;
return "translate(" + xshift + ", " + yshift + ")";
})
.attr("d", function(d,i) {
var xdist = Math.abs(d.source.x - d.target.x);
arc.radius(xdist / 2);
var points = d3.range(0, Math.ceil(xdist / 3));
radians.domain([0, points.length - 1]);
return arc(points);
});
}
Any pointers on how I might start approaching the problem?
Here is a bl.ock for reference. It shows your original paths in gray, and the proposed paths in red.
First store the counts for how many times a given path occurs:
graph.links.forEach(function(d,i) {
var pathCount = 0;
for (var j = 0; j < i; j++) {
var otherPath = graph.links[j];
if (otherPath.source === d.source && otherPath.target === d.target) {
pathCount++;
}
}
d.pathCount = pathCount;
});
Then once you have that data, I would use an ellipse instead of a radial line since it appears the radial line can only draw a curve for a circle:
d3.select("#plot").selectAll(".ellipse-link")
.data(links)
.enter().append("ellipse")
.attr("fill", "transparent")
.attr("stroke", "gray")
.attr("stroke-width", 1)
.attr("cx", function(d) {
return (d.target.x - d.source.x) / 2 + radius;
})
.attr("cy", pad)
.attr("rx", function(d) {
return Math.abs(d.target.x - d.source.x) / 2;
})
.attr("ry", function(d) {
return 150 + d.pathCount * 20;
})
.attr("transform", function(d,i) {
var xshift = d.source.x - radius;
var yshift = yfixed;
return "translate(" + xshift + ", " + yshift + ")";
});
Note that changing the value for ry above will change the heights of different curves.
Finally you'll have to use a clippath to restrict the area of each ellipse that's actually shown, so that they only display below the nodes. (This is not done in the bl.ock)

Categories

Resources