d3 Pie Chart Overlapping Labels - javascript

I know there are other solutions to this problem, but honestly they are very hard to parse out and implement into my own code. If someone can point me in the right direction in the code snippets below, I would greatly appreciate it.
ISSUE: Overlapping lines in d3 drilldown pie chart:
GOAL: To show d3 drilldown pie chart without overlapping labels.
VisualizationScopeModel.prototype.renderDrilldownPieGraph = function (data) {
let width = 950,
height = 500,
margin = 75,
radius = Math.min(width - margin, height - margin) / 2,
pieChart = d3.layout.pie().sort(null).value(function (d) {
return d.hasOwnProperty('genres') ? d.genres.length : d.count;
}),
arc = d3.svg.arc().outerRadius(radius),
outerArc = d3.svg.arc()
.innerRadius(radius * 0.9)
.outerRadius(radius * 0.9),
polylineArc = d3.svg.arc()
.innerRadius(radius * 0.7)
.outerRadius(radius * 0.9);
// clear existing svg element
d3.select("svg").text("");
let svg = d3.select("#my_dataviz")
.append("svg"),
defs = svg.append("svg:defs"),
// Declare a main gradient with the dimensions for all gradient entries to refer
mainGrad = defs.append("svg:radialGradient")
.attr("gradientUnits", "userSpaceOnUse")
.attr("cx", 0).attr("cy", 0).attr("r", radius).attr("fx", 0).attr("fy", 0)
.attr("id", "master"),
// The pie sectors container
arcGroup = svg.append("svg:g")
.attr("class", "arcGroup")
.attr("filter", "url(#shadow)")
.attr("transform", "translate(" + (width / 2) + "," + (height / 2) + ")"),
// The sector text labels
labels = svg.append("g")
.attr("class", "labels")
.attr("transform", "translate(" + (width / 2) + "," + (height / 2) + ")"),
// The text lines point a sector
lines = svg.append("g")
.attr("class", "lines")
.attr("transform", "translate(" + (width / 2) + "," + (height / 2) + ")");
// Declare shadow filter
let shadow = defs.append("filter").attr("id", "shadow")
.attr("filterUnits", "userSpaceOnUse")
.attr("x", -1 * (width / 2)).attr("y", -1 * (height / 2))
.attr("width", width).attr("height", height);
shadow.append("feGaussianBlur")
.attr("in", "SourceAlpha")
.attr("stdDeviation", "4")
.attr("result", "blur");
shadow.append("feOffset")
.attr("in", "blur")
.attr("dx", "4").attr("dy", "4")
.attr("result", "offsetBlur");
shadow.append("feBlend")
.attr("in", "SourceGraphic")
.attr("in2", "offsetBlur")
.attr("mode", "normal");
// "Fold" pie sectors by tweening its current start/end angles
// into 2*PI
function tweenOut(data) {
data.startAngle = data.endAngle = (2 * Math.PI);
var interpolation = d3.interpolate(this._current, data);
this._current = interpolation(0);
return function (t) {
return arc(interpolation(t));
};
}
// "Unfold" pie sectors by tweening its start/end angles
// from 0 into their final calculated values
function tweenIn(data) {
var interpolation = d3.interpolate({ startAngle: 0, endAngle: 0 }, data);
this._current = interpolation(0);
return function (t) {
return arc(interpolation(t));
};
}
// Helper function to extract color from data object
function getColor(data, index) {
return data.color;
}
// Helper function to extract a darker version of the color
function getDarkerColor(data, index) {
return d3.rgb(getColor(data, index)).darker();
}
function findChildenByName(name) {
for (i = 0; i < data.length; i++) {
if (data[i].name == name) {
return data[i].genres;
}
}
return data;
}
// Redraw the graph given a certain level of data
function updateGraph(name) {
var currData = data;
if (name != undefined) currData = findChildenByName(name);
// Create a gradient for each entry (each entry identified by its unique category)
var gradients = defs.selectAll(".gradient").data(currData, function (d) { return d.name; });
gradients.enter().append("svg:radialGradient")
.attr("id", function (d, i) { return "gradient" + d.name; })
.attr("class", "gradient")
.attr("xlink:href", "#master");
gradients.text("").append("svg:stop").attr("offset", "0%").attr("stop-color", getColor);
gradients.append("svg:stop").attr("offset", "90%").attr("stop-color", getColor);
gradients.append("svg:stop").attr("offset", "100%").attr("stop-color", getDarkerColor);
// Create a sector for each entry in the enter selection
var paths = arcGroup.selectAll("path")
.data(pieChart(currData), function (d) { return d.data.name; });
paths.enter().append("svg:path").attr("class", "sector");
// Each sector will refer to its gradient fill
paths.attr("fill", function (d, i) { return "url(#gradient" + d.data.name + ")"; })
.transition().duration(1000)
.attrTween("d", tweenIn).each("end", function () {
this._listenToEvents = true;
});
// Mouse interaction handling
paths.on("click", function (d) {
if (this._listenToEvents) {
// Reset inmediatelly
d3.select(this).attr("transform", "translate(0,0)")
// Change level on click if no transition has started
paths.each(function () {
this._listenToEvents = false;
});
if (d.data.genres) updateGraph(d.data.name);
else updateGraph()
}
}).on("mouseover", function (d) {
// Mouseover effect if no transition has started
if (this._listenToEvents) {
// Calculate angle bisector
var ang = d.startAngle + (d.endAngle - d.startAngle) / 2;
// Transformate to SVG space
ang = (ang - (Math.PI / 2)) * -1;
// Calculate a 10% radius displacement
var x = Math.cos(ang) * radius * 0.1;
var y = Math.sin(ang) * radius * -0.1;
d3.select(this).transition()
.duration(250).attr("transform", "translate(" + x + "," + y + ")");
}
}).on("mouseout", function (d) {
// Mouseout effect if no transition has started
if (this._listenToEvents) {
d3.select(this).transition()
.duration(150).attr("transform", "translate(0,0)");
}
});
// Collapse sectors for the exit selection
paths.exit().transition().duration(1000)
.attrTween("d", tweenOut).remove();
// Create sector texts
var texts = labels.text("").selectAll('text')
.data(pieChart(currData), function (d) { return d.data.org_name; });
texts.enter().append("text").attr("dy", ".75em")
.text(function (d) {
return `${d.data.org_name} (${d.data.hasOwnProperty('genres') ? d.data.genres.length : d.data.count})`;
});
function midAngle(d) {
return d.startAngle + (d.endAngle - d.startAngle) / 2;
}
texts.transition().duration(1000)
.attrTween("transform", function (d) {
this._current = this._current || d;
var interpolate = d3.interpolate(this._current, d);
this._current = interpolate(0);
return function (t) {
var d2 = interpolate(t);
var pos = outerArc.centroid(d2);
pos[0] = (radius + margin / 2) * (midAngle(d2) < Math.PI ? 1 : -1);
return "translate(" + pos + ")";
};
})
.styleTween("text-anchor", function (d) {
this._current = this._current || d;
var interpolate = d3.interpolate(this._current, d);
this._current = interpolate(0);
return function (t) {
var d2 = interpolate(t);
return midAngle(d2) < Math.PI ? "start" : "end";
};
});
texts.exit().transition().duration(1000)
.attrTween("d", tweenOut).remove();
// Create text lines point sectors
var polylines = lines.text("").selectAll("polyline")
.data(pieChart(currData), function (d) { return d.data.name; });
polylines.enter().append("polyline");
polylines.transition().duration(1000)
.attrTween("points", function (d) {
this._current = this._current || d;
var interpolate = d3.interpolate(this._current, d);
this._current = interpolate(0);
return function (t) {
var d2 = interpolate(t);
var pos = outerArc.centroid(d2);
pos[0] = (radius + margin / 2) * (midAngle(d2) < Math.PI ? 1 : -1);
return [polylineArc.centroid(d2), outerArc.centroid(d2), pos];
};
});
polylines.exit().transition().duration(1000)
.attrTween("d", tweenOut).remove();
}
updateGraph();
}

Related

Replicate pure JS version of D3 Zoomable Sunburst (observable)

I have been trying to replicate https://beta.observablehq.com/#mbostock/d3-zoomable-sunburst this in pure JS to use it in one of my projects. I am using PHP and Ajax to load Dynamic Data in JavaScript. I think the code in the Observable link is not in pure JS but rather Node or something else.
I am a newbie in Scripting, hence it is becoming very difficult for me to understand the written code. I do know that a pure JS will need the data (flare.json) in a specific format, which will generate the expected output. I can control the JSON structure from the backend, but I am unable to generate an output like the link.
I have followed multiple examples online:
https://bl.ocks.org/mbostock/4348373
And the same in d3 version 4 (which is very similar to v5, used in the Observable example):
https://bl.ocks.org/maybelinot/5552606564ef37b5de7e47ed2b7dc099
I have been trying to convert the Observable Zoomable Sunburst into JS functions, but I am unable to make it work. I have the exact same flare.json file and tried to recreate exact functions as therein Observable one. But it still is not working.
I am attaching my work. How can I get it working?
Sample Work
I have also tried to seek help on the Google Groups for d3-js, but I haven't got any help from there too.
The closest possible output which I have achieved till now is mentioned below:
var margin = {top: 288, right: 416, bottom: 288, left: 416},
radius = Math.min(margin.top, margin.right, margin.bottom, margin.left) - 5;
var hue = d3.scale.category10();
var luminance = d3.scale.sqrt()
.domain([0, 1e6])
.clamp(true)
.range([90, 20]);
var svg = d3.select("body").append("svg")
.attr("width", margin.left + margin.right)
.attr("height", margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var partition = d3.layout.partition()
.sort(function(a, b) { return d3.ascending(a.name, b.name); })
.size([2 * Math.PI, radius]);
var arc = d3.svg.arc()
.startAngle(function(d) { return d.x; })
.endAngle(function(d) { return d.x + d.dx ; })
.padAngle(.01)
.padRadius(radius / 3)
.innerRadius(function(d) { return radius / 3 * d.depth; })
.outerRadius(function(d) { return radius / 3 * (d.depth + 1) - 1; });
// d3.json("https://api.myjson.com/bins/byw4q", function(error, root) {
d3.json("https://gist.githubusercontent.com/mbostock/4348373/raw/85f18ac90409caa5529b32156aa6e71cf985263f/flare.json", function(error, root) {
if (error) throw error;
// Compute the initial layout on the entire tree to sum sizes.
// Also compute the full name and fill color for each node,
// and stash the children so they can be restored as we descend.
partition
.value(function(d) { return d.size; })
.nodes(root)
.forEach(function(d) {
d._children = d.children;
d.sum = d.value;
d.key = key(d);
d.fill = fill(d);
});
// Now redefine the value function to use the previously-computed sum.
partition
.children(function(d, depth) { return depth < 2 ? d._children : null; })
.value(function(d) { return d.sum; });
var center = svg.append("circle")
.attr("r", radius / 3)
.on("click", zoomOut);
center.append("title")
.text("zoom out");
var path = svg.selectAll("path")
.data(partition.nodes(root).slice(1))
.enter().append("path")
.attr("d", arc)
.style("fill", function(d) { return d.fill; })
.each(function(d) { this._current = updateArc(d); })
.on("click", zoomIn);
function zoomIn(p) {
if (p.depth > 1) p = p.parent;
if (!p.children) return;
zoom(p, p);
}
function zoomOut(p) {
if (!p.parent) return;
zoom(p.parent, p);
}
// Zoom to the specified new root.
function zoom(root, p) {
if (document.documentElement.__transition__) return;
// Rescale outside angles to match the new layout.
var enterArc,
exitArc,
outsideAngle = d3.scale.linear().domain([0, 2 * Math.PI]);
function insideArc(d) {
return p.key > d.key
? {depth: d.depth - 1, x: 0, dx: 0} : p.key < d.key
? {depth: d.depth - 1, x: 2 * Math.PI, dx: 0}
: {depth: 0, x: 0, dx: 2 * Math.PI};
}
function outsideArc(d) {
return {depth: d.depth + 1, x: outsideAngle(d.x), dx: outsideAngle(d.x + d.dx) - outsideAngle(d.x)};
}
center.datum(root);
// When zooming in, arcs enter from the outside and exit to the inside.
// Entering outside arcs start from the old layout.
if (root === p) enterArc = outsideArc, exitArc = insideArc, outsideAngle.range([p.x, p.x + p.dx]);
path = path.data(partition.nodes(root).slice(1), function(d) { return d.key; });
// When zooming out, arcs enter from the inside and exit to the outside.
// Exiting outside arcs transition to the new layout.
if (root !== p) enterArc = insideArc, exitArc = outsideArc, outsideAngle.range([p.x, p.x + p.dx]);
d3.transition().duration(d3.event.altKey ? 7500 : 750).each(function() {
path.exit().transition()
.style("fill-opacity", function(d) { return d.depth === 1 + (root === p) ? 1 : 0; })
.attrTween("d", function(d) { return arcTween.call(this, exitArc(d)); })
.remove();
path.enter().append("path")
.style("fill-opacity", function(d) { return d.depth === 2 - (root === p) ? 1 : 0; })
.style("fill", function(d) { return d.fill; })
.on("click", zoomIn)
.each(function(d) { this._current = enterArc(d); });
path.transition()
.style("fill-opacity", 1)
.attrTween("d", function(d) { return arcTween.call(this, updateArc(d)); });
});
}
});
function key(d) {
var k = [], p = d;
while (p.depth) k.push(p.name), p = p.parent;
return k.reverse().join(".");
}
function fill(d) {
var p = d;
while (p.depth > 1) p = p.parent;
var c = d3.lab(hue(p.name));
c.l = luminance(d.sum);
return c;
}
function arcTween(b) {
var i = d3.interpolate(this._current, b);
this._current = i(0);
return function(t) {
return arc(i(t));
};
}
function updateArc(d) {
return {depth: d.depth, x: d.x, dx: d.dx};
}
d3.select(self.frameElement).style("height", margin.top + margin.bottom + "px");
<!DOCTYPE html>
<meta charset="utf-8">
<style>
circle,
path {
cursor: pointer;
}
circle {
fill: none;
pointer-events: all;
}
</style>
<body>
<script src="https://d3js.org/d3.v3.min.js"></script>
</body>
The code written is pure javascript tough, whatever the data you are getting from Ajax the same endpoint you just need to pass here,
The example I am running here means the same should work in your project as well, Instead of calling the Ajax you can pass your Json in this line
d3.json("https://gist.githubusercontent.com/mbostock/4348373/raw/85f18ac90409caa5529b32156aa6e71cf985263f/flare.json", function(error, root)
<!DOCTYPE html>
<meta charset="utf-8">
<style>
path {
stroke: #fff;
}
</style>
<body>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
var width = 960,
height = 700,
radius = (Math.min(width, height) / 2) - 10;
var formatNumber = d3.format(",d");
var x = d3.scaleLinear()
.range([0, 2 * Math.PI]);
var y = d3.scaleSqrt()
.range([0, radius]);
var color = d3.scaleOrdinal(d3.schemeCategory20);
var partition = d3.partition();
var arc = d3.arc()
.startAngle(function(d) {
return Math.max(0, Math.min(2 * Math.PI, x(d.x0)));
})
.endAngle(function(d) {
return Math.max(0, Math.min(2 * Math.PI, x(d.x1)));
})
.innerRadius(function(d) {
return Math.max(0, y(d.y0));
})
.outerRadius(function(d) {
return Math.max(0, y(d.y1));
});
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + (height / 2) + ")");
d3.json("https://gist.githubusercontent.com/mbostock/4348373/raw/85f18ac90409caa5529b32156aa6e71cf985263f/flare.json", function(error, root) {
if (error) throw error;
root = d3.hierarchy(root);
root.sum(function(d) {
return d.size;
});
svg.selectAll("path")
.data(partition(root).descendants())
.enter().append("path")
.attr("d", arc)
.style("fill", function(d) {
return color((d.children ? d : d.parent).data.name);
})
.on("click", click)
.append("title")
.text(function(d) {
return d.data.name + "\n" + formatNumber(d.value);
});
function labelVisible(d) {
return d.y1 <= 3 && d.y0 >= 1 && (d.y1 - d.y0) * (d.x1 - d.x0) > 0.03;
}
function labelTransform(d) {
const x = (d.x0 + d.x1) / 2 * 180 / Math.PI;
const y = (d.y0 + d.y1) / 2 * radius;
return `rotate(${x - 90}) translate(${y},0) rotate(${x < 180 ? 0 : 180})`;
}
svg.selectAll("text")
.attr("dy", "0.35em")
.attr("pointer-events", "none")
.attr("text-anchor", "middle")
.style("user-select", "none")
.attr("fill-opacity", d => +labelVisible(d.current))
.attr("transform", d => labelTransform(d.current))
.data(root.descendants().slice(1))
.enter().append("text")
.text(d => d.data.name);
});
function click(d) {
svg.transition()
.duration(750)
.tween("scale", function() {
var xd = d3.interpolate(x.domain(), [d.x0, d.x1]),
yd = d3.interpolate(y.domain(), [d.y0, 1]),
yr = d3.interpolate(y.range(), [d.y0 ? 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");
</script>

d3 v4 donut chart has text and poylines that overlap with small values

I am trying to figure out how to arrange the labels so that they do not overlap. Here is a picture of the chart
As you can see, with really small values, the text labels overlap. I tried to iterate over each text element and modify it's position, but that doesn't seem to be working. You can see at the bottom of this function that I tried to get the position of each text element and then modify it. What am I doing wrong? I've been at it for hours.
_renderDonutChart() {
let self = this;
// console.log("Donut Chart is beginning render")
let textOffset = 14;
self.graph.data[0].forEach(function (d) {
d.value = +d.value;
})
console.log(self.graph.data[0])
let boxSize = (self.options.radius + self.options.padding) * 2;
let parent = d3.select(self.ui.parent);
//let color = d3.scaleOrdinal(['#dc8710', '#9e3400', '#f19b12']);
let color = d3.scaleOrdinal(d3.schemeCategory20c);
let svg = parent.append('svg')
.attr('width', boxSize * 2)
.attr('height', boxSize)
.attr('transform', 'translate(-111,0)')
.append('g')
.attr('transform', 'translate(' + boxSize + ',' + boxSize / 2 + ')');
svg.append('g')
.attr('class', 'slices')
svg.append("g")
.attr("class", "labelName")
svg.append("g")
.attr("class", "labelValue")
svg.append("g")
.attr("class", "lines")
svg.append("div")
.attr("class", "progress-circle__box progress-circle__box--victorytype")
let arc = d3.arc()
.innerRadius(self.options.radius - self.options.border)
.outerRadius(self.options.radius);
let outerArc = d3.arc()
.innerRadius((self.options.radius - self.options.border) * 1.2)
.outerRadius((self.options.radius) * 1.2);
let legendRectSize = self.options.radius * 0.05;
let legendSpacing = self.options.radius * 0.02;
let pie = d3.pie()
.value(function(d) { return d.value; })
.sort(null);
let slice = svg.select('.slices')
.selectAll('path.slice')
.data(pie(self.graph.data[0]))
.enter()
.append('path')
.attr("class", "slice")
.attr('d', arc)
.attr('fill', function(d, i) {
return color(d.data.label);
})
.transition().duration(1000)
.attrTween("d", function(d) {
this._current = this._current || 0;
var interpolate = d3.interpolate(this._current, d);
this._current = interpolate(0);
return function(t) {
return arc(interpolate(t));
};
})
function midAngle(d){
return d.startAngle + (d.endAngle - d.startAngle)/2;
}
let text = svg.select(".labelName").selectAll("text")
.data(pie(self.graph.data[0]))
.enter()
.append("text")
.attr('class', 'label')
.attr("dy", ".35em")
.attr('transform', function(d) {
// effectively computes the centre of the slice.
// see https://github.com/d3/d3-shape/blob/master/README.md#arc_centroid
var pos = outerArc.centroid(d);
// changes the point to be on left or right depending on where label is.
pos[0] = self.options.radius * 0.97 * (midAngle(d) < Math.PI ? 1 : -1);
return 'translate(' + pos + ')';
})
.style('text-anchor', function(d) {
// if slice centre is on the left, anchor text to start, otherwise anchor to end
return (midAngle(d)) < Math.PI ? 'start' : 'end';
})
.style("fill", "white")
.text(function(d) {
return (" " + d.data.label+": " +d.value+"");
})
.transition().duration(1000)
.attrTween("transform", function(d) {
this._current = this._current || d;
var interpolate = d3.interpolate(this._current, d);
this._current = interpolate(0);
return function(t) {
var d2 = interpolate(t);
var pos = outerArc.centroid(d2);
pos[0] = self.options.radius * (midAngle(d2) < Math.PI ? 1 : -1);
return "translate("+ pos +")";
};
})
.styleTween("text-anchor", function(d){
this._current = this._current || d;
var interpolate = d3.interpolate(this._current, d);
this._current = interpolate(0);
return function(t) {
var d2 = interpolate(t);
return midAngle(d2) < Math.PI ? "start":"end";
};
})
.text(function(d) {
return (d.data.label+": "+d.value+"%");
})
let polyline = svg.select(".lines").selectAll("polyline")
.data(pie(self.graph.data[0]))
.enter()
.append("polyline")
.attr('points', function(d) {
var pos = outerArc.centroid(d);
pos[0] = self.options.radius * 0.95 * (midAngle(d) < Math.PI ? 1 : -1);
return [arc.centroid(d), outerArc.centroid(d), pos]
})
.style("fill", "none")
.style("stroke", "white")
.style("stroke-width", "1px");
let prev;
text.each(function(d, i) {
if(i > 0) {
let thisbb = this.getBoundingClientRect(),
prevbb = prev.getBoundingClientRect();
// move if they overlap
console.log(thisbb.left);
console.log(prevbb.right);
if(!(thisbb.right < prevbb.left ||
thisbb.left > prevbb.right ||
thisbb.bottom < prevbb.top ||
thisbb.top > prevbb.bottom)) {
var ctx = thisbb.left + (thisbb.right - thisbb.left)/2,
cty = thisbb.top + (thisbb.bottom - thisbb.top)/2,
cpx = prevbb.left + (prevbb.right - prevbb.left)/2,
cpy = prevbb.top + (prevbb.bottom - prevbb.top)/2,
off = Math.sqrt(Math.pow(ctx - cpx, 2) + Math.pow(cty - cpy, 2))/2;
d3.select(this).attr("transform",
"translate(" + Math.cos(((d.startAngle + d.endAngle - Math.PI) / 2)) *
(self.options.radius + textOffset + off) + "," +
Math.sin((d.startAngle + d.endAngle - Math.PI) / 2) *
(self.options.radius + textOffset + off) + ")");
}
}
prev = this;
});
// console.log("Donut Chart is ending render")
}
I had the same issue. The best solution for me was to increase the size of the Svg area (based on current window) and add some padding to the legends based on the radius of the donut.
ie:
var margin = {top: 20, right: 100, bottom: 30, left: 40};
var svgWidth = window.innerWidth - (window.innerWidth/4);
var width = svgWidth,
height = (Math.min(width) / 2) + 100,
radius = Math.min(width, height) / 3;
var legendRectSize = (radius * 0.08);
var legendSpacing = (radius * 0.05);
This worked for me until the item count became too high. I amended from labels around the edge to display in the center, on slice/arc hover events. (There is an animation on the data value, which is why they are slanted in this snapshot)
I thought this made the unreadable data labels less confusing.
I thing you had same problem with this, you need to add more margin to your pie chart segment, assume your pie return 3% value, and you draw a text with line, it will stack on another, you need to devine margin if that value < 3 draw text with y+margin
if(percent<3){
o[1]
pos[1] += i*15
}
//return [label.centroid(d),[o[0],0[1]] , pos];
return [label.centroid(d),[o[0],pos[1]] , pos];
})
try to append this code structure to your chart

Updating D3 V3 Zoomable Sunburst data breaks chart

I'm new to programming D3, we are having a problem updating the sunburst chart without causing it to break its transitions. When the data is changed, parts of the chart stop resizing for the zooming feature and appear to be over each other. Additionally, I get errors resulting, apparently, from the interpolation of the flags each "arc" in the graph (i.e. Error: attribute d: Expected arc flag ('0' or '1'), "…125862517296 0 0.987032831999999…".). We are using a tween that is specific for this problem and thus can't understand what might be causing it.
Since I'm inexperienced in this language I've spent several days on this problem only to see it come back. If any of you could help I would appreciate it immensely.
Here's a working fiddle that I created to illustrate our problem:
- https://jsfiddle.net/v9ab0vms/1/
The problem can be seen by waiting 5 seconds and clicking the "Commercial" and then "Communications" arcs. Causing the transition errors and breaking some of the arcs. Zooming the graph while the data gets changed causes it to break in other ways, any help on that would also be greatly appreciated. I was just trying to fix it step by step.
Picture of broken chart arcs
The code has to be posted as well so I'm just inserting the two main functions for the sunburst:
function genSunburst2() {
var width = d3.select("#container_sunburst").style("width").split("px")[0],
height = d3.select("#container_sunburst").style("height").split("px")[0],
radius = Math.min(width, height) / 2;
var x = d3.scale.linear()
.range([0, 2 * Math.PI]);
var y = d3.scale.sqrt()
.range([0, radius]);
var color = d3.scale.category20c();
var svg = d3.select("#sunburst")
.attr("id", "sunburst")
.attr("width", width)
.attr("height", height)
.select("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
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));
});
function computeTextRotation(d) {
var angle = x(d.x + d.dx / 2) - Math.PI / 2;
return angle / Math.PI * 180;
}
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);
};
};
}
function arcTweenUpdate(a) {
console.log(path);
var _self = this;
var i = d3.interpolate({
x: this.x0,
dx: this.dx0
}, a);
return function(t) {
var b = i(t);
console.log(window);
_self.x0 = b.x;
_self.dx0 = b.dx;
return arc(b);
};
}
updateSunburst3 = function() {
if (sunburstClick) {
sunburstClick = false;
return;
}
var root = createJsonDataset();
// DATA JOIN - Join new data with old elements, if any.
var gs = svg.selectAll("g").data(partition.nodes(root));
// ENTER
var g = gs.enter().append("g").on("click", click);
// UPDATE
var path = g.append("path");
gs.select('path')
.style("fill", function(d) {
return color(d.name);
})
.on("click", click)
.each(function(d) {
this.x0 = d.x;
this.dx0 = d.dx;
})
.transition().duration(500)
.attr("d", arc);
var text = g.append("text");
gs.select('text')
.attr("x", function(d) {
return y(d.y);
})
.attr("dx", "6") // margin
.attr("dy", ".35em") // vertical-align
.attr("transform", function(d) {
return "rotate(" + computeTextRotation(d) + ")";
})
.text(function(d) {
return d.name;
})
.style("fill", "white");
function click(d) {
sunburstClick = true;
console.log(d);
// fade out all text elements
/*if (d.size !== undefined) {
d.size += 100;
};*/
text.transition().attr("opacity", 0);
console.log(path);
for (var i = 0; i < path[0].length; ++i) {
if (path[0][i] === undefined || path[0][i] === null) {
path[0].splice(i, 1);
--i;
}
}
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);
});
}
});
userSelection = undefined;
purposeSelection = undefined;
// TODO: alterar para ele ter em conta a hierarquia da selecao corrente
// para so sair de uma hierarquia
if (typeof d.name != "undefined" && d.name != "") {
if (typeof d.size === "undefined") {
userSelection = d.name;
purposeSelection = undefined;
} else {
userSelection = d.parent.name;
purposeSelection = d.name;
}
} else {
// if only the user was selected, back out to no selection
if (purposeSelection === undefined && userSelection != undefined) {
userSelection = undefined;
// if both the user and the purpose were selected, back out to just user selection
} else if (purposeSelection != undefined && userSelection != undefined) {
purposeSelection = undefined;
}
}
applySelection();
}
// EXIT - Remove old elements as needed.
gs.exit().transition().duration(500).style("fill-opacity", 1e-6).remove();
}
}
Thank you in advance
Edit: To clarify:
This is in D3 V3.
We have two variables one with the full dataset
and another with the "working" dataset. We apply filters to the full
dataset and store the result in the working dataset.

D3.js rotate shape without rotating text inside

I want to create rotate animation in a d3.js chart. With help of Gilsha I was able to create a chart like this :
Now on click of any of the outer circles i want to rotate all circles and align the one which was clicked in the place of "hllooo" circle.
I have copied one angletween which is rotating the circles but the problem is that text inside that circle also gets rotated and creates something like this:
As you can see the text is also rotated which i dont want. The code for transform function is:
function angleTween(d, i) {
var angle = 360 - ((i + 1) * 70);
var i = d3.interpolate(0, 90);
return function (t) {
return "rotate(" + i(t) + ")";
};
}
So how to keep text unrotated and just rotate the shape ?
Calulcate the x and y attributes of text elements by using SVGPoint matrixTransform.
earth.on('click', function() {
texts.style("opacity", 0);
earthLayer
.transition()
.duration(2000)
.attrTween("transform", angleTween)
.each("end", function() {
var svgEl = this.ownerSVGElement;
var angle = d3.select(this).attr("transform").match(/\d+/g)[0];
var matrix = svgEl.createSVGMatrix().rotate(angle);
texts.each(function(d, i) {
var point = this.ownerSVGElement.createSVGPoint();
point.x = +d.cx;
point.y = +d.cy;
point = point.matrixTransform(matrix);
d3.select(this).attr("x", point.x).attr("y", point.y);
});
texts.style("opacity", 1);
});
});
var now = d3.time.year.floor(new Date());
var spacetime = d3.select('body');
var width = 960,
height = 700,
radius = Math.min(width, height);
var radii = {
"sun": radius / 6,
"earthOrbit": radius / 2.5,
"earth": radius / 15,
"moonOrbit": radius / 16,
"moon": radius / 96
};
// Space
var svg = spacetime.append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
// Sun
var sun = svg.append("circle")
.attr("class", "sun")
.attr("r", radii.sun)
//.style("fill", "rgba(255, 204, 0, 1.0)");
.style("stroke", "#f58c2e")
.style("stroke-width", "10")
.style("fill", "none");
// Earth's orbit
var orbit = svg.append("circle")
.attr("class", "earthOrbit")
.attr("r", radii.earthOrbit)
.style("fill", "none")
.style("stroke", "#bababa")
.style("stroke-width", "30");
// Current position of Earth in its orbit
var earthOrbitPosition = d3.svg.arc()
.outerRadius(radii.earthOrbit + 1)
.innerRadius(radii.earthOrbit - 1)
.startAngle(0)
.endAngle(0);
svg.append("path")
.attr("class", "earthOrbitPosition")
.attr("d", earthOrbitPosition)
.style("fill", "rgba(255, 204, 0, 0.75)");
// Time of day
var day = d3.svg.arc()
.outerRadius(radii.earth)
.innerRadius(0)
.startAngle(0)
.endAngle(0);
svg.append("path")
.attr("class", "day")
.attr("d", day)
.attr("transform", "translate(0," + -radii.earthOrbit + ")")
.style("fill", "rgba(53, 110, 195, 1.0)");
// Current position of the Moon in its orbit
var moonOrbitPosition = d3.svg.arc()
.outerRadius(radii.moonOrbit + 1)
.innerRadius(radii.moonOrbit - 1)
.startAngle(0)
.endAngle(0);
svg.append("path")
.attr("class", "moonOrbitPosition")
.attr("d", moonOrbitPosition)
.attr("transform", "translate(0," + -radii.earthOrbit + ")")
.style("fill", "rgba(113, 170, 255, 0.75)");
function getCirclePoints(points, radius, center) {
var circlePositions = [];
var slice = 2 * Math.PI / points;
for (var i = 0; i < points; i++) {
var angle = slice * i;
var newX = (center.X + radius * Math.cos(angle));
var newY = (center.Y + radius * Math.sin(angle));
circlePositions.push({
cx: newX,
cy: newY
});
}
return circlePositions;
}
var circlePositions = getCirclePoints(10, radii.earthOrbit, {
X: 0,
Y: 0
});
var earthLayer = svg.append("g").classed("earthLayer", true);
var textLayer = svg.append("g").classed("textLayer", true);
var earth = earthLayer.selectAll(".earth").data(circlePositions)
.enter()
.append("circle")
.attr("cx", function(d) {
return d.cx;
})
.attr("cy", function(d) {
return d.cy;
})
.attr("class", "earth")
.style("fill", "white")
.attr("r", radii.earth)
.style("stroke", "#bababa")
.style("stroke-width", "10");
texts = textLayer.selectAll("text").data(circlePositions).enter().append("text").attr("x", function(d) {
return d.cx
}).attr("dx", -radii.earth / 2).attr("y", function(d) {
return d.cy
}).text(function(d, i) {
if (i == 0) return "hllooo";
else return "hllooo" + i;
});
earth.on('click', function() {
texts.style("opacity", 0);
earthLayer
.transition()
.duration(2000)
.attrTween("transform", angleTween)
.each("end", function() {
var svgEl = this.ownerSVGElement;
var angle = d3.select(this).attr("transform").match(/\d+/g)[0];
var matrix = svgEl.createSVGMatrix().rotate(angle);
texts.each(function(d, i) {
var point = this.ownerSVGElement.createSVGPoint();
point.x = +d.cx;
point.y = +d.cy;
point = point.matrixTransform(matrix);
d3.select(this).attr("x", point.x).attr("y", point.y);
});
texts.style("opacity", 1);
});
});
function angleTween(d, i) {
var angle = 360 - ((i + 1) * 70);
var i = d3.interpolate(0, angle);
return function(t) {
return "rotate(" + i(t) + ")";
};
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
Hope this helps.

Javascript array filter | Custom method

I have a problem. Look at the picture below.
The data for my pie is:
var data = [{statusid:"Paid", price:10},
{statusid:"Not accepted", price:20},
{statusid:"Canceled", price:20},
{statusid:"Waiting", price:1},
{statusid:"Refunded", price:1},
{statusid:"Pending", price:1}];
Is it possible to solve this problem by sorting input data? How?
Thanks!
P.S: D3.js code below:
var data = [{statusid:"Paid", price:10}, {statusid:"Not accepted", price:20}, {statusid:"Canceled", price:20}, {statusid:"Waiting", price:1}, {statusid:"Refunded", price:1}, {statusid:"Pending", price:1}];
var sumprice = 0;
for (var i in data) {
sumprice += data[i].price;
}
/* ==============================================================
Setting up the chart
============================================================== */
var width = 600,
height = 500,
radius = Math.min(width, height) / 4;
var color = d3.scale.ordinal()
.range(["#227fbc", "#64a620", "#7b6888", "#a15c57", "#961819", "#d8d13a", "#d3723b"]);
var arc = d3.svg.arc()
.outerRadius(radius * 0.8)
.innerRadius(radius * 0.4);
var outerArc = d3.svg.arc()
.innerRadius(radius * 0.9)
.outerRadius(radius * 0.9);
var pie = d3.layout.pie()
.sort(null)
.value(function(d) { return d.price; });
/* ==============================================================
Creating the svg element
============================================================== */
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
svg.append("g")
.attr("class", "lines");
svg.append("g")
.attr("class", "labels");
/* ==============================================================
Drawning the pizza
============================================================== */
var g = svg.selectAll(".arc")
.data(pie(data))
.enter().append("g")
.attr("class", "arc");
g.append("path")
.attr("d", arc)
.style("fill", function(d) {
return color(d.data.statusid);
});
/* ==============================================================
Drawning the labels
============================================================== */
var key = function(d){ return d.data.statusid; };
var text = svg.select(".labels").selectAll("text")
.data(pie(data), key);
text.enter()
.append("text")
.attr("dy", ".35em")
.text(function(d) {
return d.data.statusid;
});
function midAngle(d){
return d.startAngle + (d.endAngle - d.startAngle)/2;
}
text.transition().duration(1000)
.attrTween("transform", function(d) {
this._current = this._current || d;
var interpolate = d3.interpolate(this._current, d);
this._current = interpolate(0);
return function(t) {
var d2 = interpolate(t);
var pos = outerArc.centroid(d2);
pos[0] = radius * (midAngle(d2) < Math.PI ? 1 : -1);
return "translate("+ pos +")";
};
})
.styleTween("text-anchor", function(d){
this._current = this._current || d;
var interpolate = d3.interpolate(this._current, d);
this._current = interpolate(0);
return function(t) {
var d2 = interpolate(t);
return midAngle(d2) < Math.PI ? "start":"end";
};
});
text.exit()
.remove();
/* ==============================================================
Drawning the inside-labels
============================================================== */
var arcs = svg.selectAll(".arc");
// Add a magnitude value to the larger arcs, translated to the arc centroid and rotated.
arcs.filter(function(d) { return d.endAngle - d.startAngle > .2; }).append("svg:text")
.attr("dy", ".35em")
.attr("text-anchor", "middle")
//.attr("transform", function(d) { return "translate(" + arc.centroid(d) + ")rotate(" + angle(d) + ")"; })
.attr("transform", function(d) { //set the label's origin to the center of the arc
//we have to make sure to set these before calling arc.centroid
d.outerRadius = arc.outerRadius; // Set Outer Coordinate
d.innerRadius = arc.outerRadius/2; // Set Inner Coordinate
console.log('omg');
return "translate(" + arc.centroid(d) + ")";
})
.style("fill", "White")
.style("font", "bold 12px Arial")
.text(function(d) { return (d.data.price*100/sumprice).toFixed(1)+'%'; });
function angle(d) {
var a = (d.startAngle + d.endAngle) * 90 / Math.PI - 90;
return a > 90 ? a - 180 : a;
}
/* ==============================================================
Drawning the label-lines
============================================================== */
var polyline = svg.select(".lines").selectAll("polyline")
.data(pie(data), key);
polyline.enter()
.append("polyline");
polyline.transition()
.attrTween("points", function(d){
this._current = this._current || d;
var interpolate = d3.interpolate(this._current, d);
this._current = interpolate(0);
return function(t) {
var d2 = interpolate(t);
var pos = outerArc.centroid(d2);
pos[0] = radius * 0.95 * (midAngle(d2) < Math.PI ? 1 : -1);
return [arc.centroid(d2), outerArc.centroid(d2), pos];
};
});
polyline.exit()
.remove();

Categories

Resources