How to get my D3.js pie charts to transition properly - javascript

I'm missing something here and I can't seem to figure it out. I have another pie chart on my page that transitions smooth, but it deals with different data. I keep getting some error of " attribute d: Expected arc flag ('0' or '1'), etc" on the transition. I've console.logged the data I'm using to draw it (the d.(x)Pct) and none of the values are negative. Basically, I'm creating the arc, then setting the endAngle on startup dynamically. Then 10 seconds later, when my update comes in, it should reset the endAngle with the new data. My d.(x)Pct data is all under 1 since 1 is a full circle. What's weird is there are 3 circles, the middle one transitions just fine, but the other 2 disappear and re-appear after the transition is over with. They are updated with the new data, but you don't see the transition considering they disappear. Code is below:
// Creates the arc for the pie chart
var pieArc = d3.svg.arc()
.startAngle(0)
.innerRadius(0)
.outerRadius(100);
// Draw the circles (carrierServices portion)
var pieChartsCarrierServices = pieChartsSvg.selectAll(".CarrierServicesPies").data(data);
pieChartsCarrierServices.enter()
.append("path")
.attr({
"class": "CarrierServicesPies",
"d": function (d) {
pieArc.endAngle((2 * Math.PI) * d.carrierServicesPct)
return pieArc();
},
"transform": function (d) {
var w = $(this).parent().width();
return "translate(" + (w/2) + "," + (yScale(d.id) + 50) + ")" // Radius divided by 2 to center with bar graphs
},
"fill": "white"
});
pieChartsCarrierServices.exit().remove();
pieChartsCarrierServices.transition().duration(750)
.attr({
"d": function (d) {
pieArc.endAngle((2 * Math.PI) * d.carrierServicesPct)
return pieArc();
}
});

Related

Best way to fit variably-sized text into a semi-circle in d3.js?

I am currently working on a visualization in d3.js with the goal to visualize data similarity. I wish to compare my data within circles by creating two semi-circles for each node and putting the comparison data within these semicircles. My data consists of strings (each semicircle receives a single sentence).
My current approach is as follows:
First, I create my necessary node data using the pack-layout.
var bubble = d3.pack().size([SVG_WIDTH,SVG_HEIGHT]).padding(CIRCLE_PADDING),
root = d3.hierarchy({children: COMPARISON_DATA}).sum(function(d){ return d.children ? 0 : d[2]});
var nodeData = bubble(root).children;
d[2] is the maximum string length of the two sentences that are being put into the semicircles and thus decides the radius of the circles.
Next, I iterate over each node and create the corresponding semicircles. I have removed all the code-parts which are irrelevant to my question.
nodeData.forEach(function (data, index) {
//upperCircleGroup simply adds a small y-translate, so that the semicircles have a margin
var gUpper = upperCircleGroup.append("g");
var gLower = lowerCircleGroup.append("g");
var lowerCircle = gLower.append('path')
.attr('d', d3.arc()({
innerRadius: 0,
outerRadius: data.r,
startAngle: Math.PI / 2,
endAngle: 3 / 2 * Math.PI
}))
.attr('transform', `translate(${data.x},${data.y})`)
var upperCircle = gUpper.append('path')
.attr('d', d3.arc()({
innerRadius: 0,
outerRadius: data.r,
startAngle: 1 / 2 * Math.PI,
endAngle: - 1 / 2 * Math.PI
}))
.attr('transform', `translate(${data.x},${data.y})`)
var upperText = gUpper
.append("foreignObject")
.attr("width", () => {return data.r*Math.sqrt(2)})
.attr("height", () => {return data.r*(Math.sqrt(2)/2)})
.attr('transform', `translate(${data.x - (data.r / Math.sqrt(2))},${data.y - (data.r/Math.sqrt(2)) })`)
.text(() => {return data.data[0]})
var lowerText = gLower
.append("foreignObject")
.attr("width", () => {return data.r*Math.sqrt(2)})
.attr("height", () => {return data.r*(Math.sqrt(2)/2)})
.attr('transform', `translate(${data.x - (data.r / Math.sqrt(2))},${data.y })`)
.text(() => {return data.data[1]})
});
As you can see, I draw my semicircles using d3's arc. Now this is where my question arises. I've had trouble putting my textual content inside the arc, so after searching for a while I chose this solution to put a div inside my semicircles which then receives the text. The sqrt(2) operations are used to fit the square into the semicircle.
My problem with this solution is, that at times, the sentence simply won't fit into the div and some content is lost. Is there a way to calculate the font-size of a string necessary, so that it fits the div of a given size? If this were possible, I could simply calculate the appropriate font-size and add a zoom option to the visualization. Also, if there are better ways to achieve what I am trying to do I would also be happy to get some feedback from you guys as I am a complete beginner when it comes to using d3.
Making text responsive to an element is difficult but CSS-Tricks have made a great article about different ways to approach it...
https://css-tricks.com/fitting-text-to-a-container/

How do I convert my scale of numbers (0-50,000) to a radial progress chart number?

I tried setting a linear range and domain to convert it, but that doesn't seem to work. I just need to create a completion chart. 50,000 being 100% and a current value of (lets say 25,000). How do I convert my normal values to radians?
Here's what I have so far:
var pieScale = d3.scale.linear().range([0, 8]);
pieScale.domain([0, 50000]);
var svg = d3.select("#redCircle").append("svg")
.attr({
'preserveAspectRatio': 'xMinYMin meet'
});
var marginArc = d3.svg.arc()
.startAngle(0)
.innerRadius(0)
.outerRadius(100);
var marginArcSvg = svg.append("path")
.attr({
"d": function (d) {
marginArc.endAngle((2 * Math.PI) * pieScale(25000));
return marginArc();
},
"transform": function (d) {
return "translate(" + 100 + ", " + 100 + ")"
},
"fill": "white"
});
Current value divided by 50,000 gives you the percentage around the circle. Then convert that to radians with the following pseudo-code
var radians = percentage * 2 * Math.PI
Source: HTML5: Fill circle/arc by percentage

placing d3 label at end of arc

Given the following speed dial, which is constructed using arcs in D3:
segmentArc = d3.svg.arc().outerRadius(radius - chartInset).innerRadius(radius - chartInset - barWidth).startAngle(arcStartRad + startPadRad).endAngle(arcEndRad - endPadRad);
How do I move the labels in each segment so that it appears right justified (at the end of each segment opposed to center)?
the labels are currently added likes this:
chart.append('text')
.attr('transform', () => {
var x = Math.round(segmentArc.centroid()[0]);
var y = Math.round(segmentArc.centroid()[1]);
return 'translate(' + x + ',' + y + ')';
})
.style("text-anchor", "middle")
.text(sectionLabel);
I solved this problem by replicating each segment arc, giving it a transparent fill and making it exactly twice as long. From there it is as simple as working out the centroid for the transparent arc.

How to implement tooltip for D3 line chart with data from 2 arrays?

I am trying to implement a D3 chart with multiple lines that shows the sample size at every point as a tooltip when hovering over the chart. I have the data as two arrays but I don't know how to access the second array relative to the position of the mouse on the chart.
How can I access the values in the second array with the correct index?
Here is a jsfiddle showing what I need to fix: http://jsfiddle.net/fMXvv/1/ on line 60 of the js, or the div.html method shown below:
graph.append("svg:path").attr("d", line(data[0])).style("stroke", "black").style("stroke-width", 2)
.on("mouseover", function (d, i) {
div.transition()
.duration(200)
.style("opacity", .9);
div.html("n = " + data[0][i])
.style("left", (d3.event.pageX) + "px")
.style("top", (d3.event.pageY - 28) + "px");
The difficulty comes from the fact that the chart has more than one series (3 lines), so knowing the x coordinate is not enough.
I used d3.event.pageY to map mouse coordinates using scale:
var toolTipScale = d3.scale.linear().domain([h + 80, 80]).range([0, max_value]);
and then you use:
div.html("n = " + Math.ceil(toolTipScale( d3.event.pageY)) )
To map mouse position correctly it is necessary to add css
body {
margin: 0;
padding: 0;
}
Here is jsfiddle of this - http://jsfiddle.net/cuckovic/sDnC8/
To access elements from second array you can use d3.event.pageX:
var iScale = d3.scale.linear().domain([w + 80, 80]).range([data[0].length, 0]);
and then:
div.html("n = " + data[1][Math.floor(iScale( d3.event.pageX))] )
jsfiddle: http://jsfiddle.net/cuckovic/sDnC8/5/
I had a problem that might have been similar to yours, I needed to select a time based on the mouse position, this is what I did (might give you some ideas):
var timeScale = d3.scale.linear()
.domain([startTime.getTime(), endTime.getTime()])
.range([30, r + 10])
.clamp(true);
axisOverlay.on("mousemove", mousemove)
.on("touchmove", mousemove);
function mousemove() {
selectedTime = new Date(timeScale.invert(d3.mouse(this)[1]));
}

Updating Text Labels in D3JS

I have been struggling with this issue for the past couple days: I have a force directed graph that labels its edges just like this example does it. The problem I am facing is that when the graph updates (ie: a node on the graph is added upon a user's click) it updates the graph but it leaves the old edge labels that I wrote previously behind:
BEFORE & AFTER A NEW GRAPH IS APPENDED:
As you can see, my edge labels are hanging around after an update. I have a function that is called everytime new data comes in, and in this function I have the following code that draws the labels:
path_text = svg.selectAll(".path")
.data(force.links(), function(d){ return d.name;})
.enter().append("svg:g");
path_text.append("svg:text")
.attr("class","path-text")
.text(function(d) { return d.data.label; });
The svg variable is declared once at a top level closure like so:
var svg = d3.select("body").append("svg:svg")
.attr("viewBox", "0 0 " + width + " " + height)
.attr("preserveAspectRatio", "xMidYMid meet");
My graph has a tick() function that calculates the location of each label like so:
function tick()
{
// Line label
path_text.attr("transform", function(d)
{
var dx = (d.target.x - d.source.x),
dy = (d.target.y - d.source.y);
var dr = Math.sqrt(dx * dx + dy * dy);
var sinus = dy/dr;
var cosinus = dx/dr;
var l = d.data.label.length * 6;
var offset = (1 - (l / dr )) / 2;
var x=(d.source.x + dx*offset);
var y=(d.source.y + dy*offset);
return "translate(" + x + "," + y + ") matrix("+cosinus+", "+sinus+",
"+-sinus+", "+cosinus+", 0 , 0)";
});
.
.
.
I have tried moving this svg declaration down into the update function, so that this is instantiated each time there is a graph change. This actually works - but it makes an entire duplicate of the entire graph. The first, original copy still keeps the old labels - but the second copy acts exactly how I want it to. Is there a way, perhaps, instead of appending svg, there is a way of replacing? I have also tried calling exit().remove() without any luck as well.
Thank you so much for your time. This has been killing me as to how I'm supposed to do this.
I placed the svg declaration inside my graph update function, attached it to a div, and clear the div before appending it again:
jQuery('#v').empty();
var svg = d3.select("#v").append("svg:svg")
.attr("viewBox", "0 0 " + width + " " + height)
.attr("preserveAspectRatio", "xMidYMid meet");
Not the cleanest solution in my opinion, but will go with this unless you all have a better solution!

Categories

Resources