I'm trying to set up a pie on d3.js that will constantly update its data (slices) and change the labels.
The slices of data may vary and are not constant (could be a 2 slices pie and 5 slices, etc.)
I've reviewed several of examples such as:
http://jsfiddle.net/emepyc/VYud2/3/
http://bl.ocks.org/mbostock/3808218
But the thing is, my implementation is quite different since I use both text lables and paths.
The problem: data is not updated and text is not removed.
Here is my code: http://jsfiddle.net/Kitt0s/vfkSs/
function tests (data) {
data = data ? data : { "slice1": Math.floor((Math.random()*10)+1), "slice2": Math.floor((Math.random()*10)+1), "slice3": Math.floor((Math.random()*10)+1), "slice4": Math.floor((Math.random()*10)+1) };
var dataa = d3.entries(data);
var cv_path = cv_svg.selectAll("g.slice")
.data(cv_pie(dataa));
cv_path.enter()
.append("g")
.attr("class", "slice")
.append("path")
.attr("fill", function(d, i) { return cv_color(i); } )
.attr("d", cv_arc)
.each(function(d) { this._current = d; });
cv_path.transition().duration(750).attrTween("d", cv_arcTween);
cv_path.exit().remove();
var cv_text = d3.selectAll(".slice")
.append("text")
.attr("transform", function(d) {
d.innerRadius = 0;
d.outerRadius = cv_r;
return "translate(" + cv_arc.centroid(d) + ")";
})
.attr("text-anchor", "middle")
.attr("font-weight", "bold")
.attr("fill", "#FFFFFF")
.attr("font-size", "30px")
.text(function(d) { return d.data.key + "(" + d.data.value + ")"; });
cv_text.exit().remove();
}
I've spent an awful lot of time trying to get this right, but still seem to get stuck (every time something else breaks.. :P)
Your help would be highly appreciated!
You were almost there. The key is to treat the text exactly the same as the paths and handle the .exit() selection by removing it. I've modified your jsfiddle here. I've removed the g elements to make it a bit easier to understand.
Related
I am trying to make a stacked bar graph through d3js and have it update when new data is passed through an update function. I call this update function to initially call the graph and it works fine. However, when I change the data and call it again, it erases all the "rect" elements from the graph (When I console log the data, it appears to be passing through). How can I make the graph be redrawn appropriately? I have tried experimenting with the .remove() statement at the beginning, but without it the data doesn't pass through when the bars are redrawn.
function update(my_data) {
svg.selectAll(".year").remove();
var year = svg.selectAll(".year")
.data(my_data)
.enter().append("g")
.attr("class", "year")
.attr("transform", function(d) { return "translate(" + x0(d.Year) + ",0)"; });
var bar = year.selectAll(".bar")
.data( function(d){ return d.locations; });
bar
.enter().append("rect")
.attr("class", "bar")
.attr("width", x0.rangeBand())
.attr("y", function(d) { return y(d.y1); })
.attr("height", function(d) { return y(d.y0) - y(d.y1); })
.style("fill", function(d) { return color(d.name); });
}
update(data);
It's hard to tell exactly what you're doing cause your question doesn't include the data or the DOM. It would help if you included a link to a work-in-progress jsFiddle or something.
If I had to guess what's going wrong, it looks like you're doing a nested join where each year gets bound to a g element and then each location gets bound to a rect inside each g element.
The issue is likely you are only specifying the enter behavior, but not the update behavior or the exit behavior. As a result, when you try to redraw, nothing updates and nothing exits - but new data elements will get added.
It would seem that is why you have to add the selectAll().remove() to get anything to redraw. By removing everything, all the data elements will trigger the enter condition and get added again.
Take a look at these tutorials to better understand how the enter/update/exit pattern works and how nested joins work.
General Update Pattern: https://bl.ocks.org/mbostock/3808218
Nested Selections: https://bost.ocks.org/mike/nest/
Also, here is a jsFiddle I wrote some time ago to demonstrate how to use nested selections and the general update pattern together:
https://jsfiddle.net/reblace/bWp8L/
var series = svg.selectAll("g.row").data(data, function(d) { return d.key; });
/*
* This section handles the "enter" for each row
*/
// Adding a g element to wrap the svg elements of each row
var seriesEnter = series.enter().append("g");
seriesEnter
.attr("class", "row")
.attr("transform", function(d, i){
return "translate(" + margin.left + "," + (margin.top + (span*i)) + ")";
})
.attr("opacity", 0).transition().duration(200).attr("opacity", 1);
// Adding a text label for each series
seriesEnter.append("text")
.style("text-anchor", "end")
.attr("x", -6)
.attr("y", boxMargin + (boxDim/2))
.attr("dy", ".32em")
.text(function(d){ return d.key; });
// nested selection for the rects associated with each row
var seriesEnterRect = seriesEnter.selectAll("rect").data(function(d){ return d.values; });
// rect enter. don't need to worry about updates/exit when a row is added
seriesEnterRect.enter().append("rect")
.attr("fill", function(d){ return colorScale(d)})
.attr("x", function(d, i){ return i*span + boxMargin; })
.attr("y", boxMargin)
.attr("height", boxDim)
.attr("width", boxDim);
/*
* This section handles updates to each row
*/
var seriesUpdateRect = series.selectAll("rect").data(function(d){ return d.values});
// rect update (Will handle updates after enter)
// rect enter
seriesUpdateRect.enter().append("rect")
.attr("x", function(d, i){ return i*span + boxMargin; })
.attr("y", boxMargin)
.attr("height", boxDim)
.attr("width", boxDim);
// rect enter + update
seriesUpdateRect
.attr("fill", function(d){ return colorScale(d)});
// Exit
seriesUpdateRect.exit();
/*
* This section handles row exit
*/
series.exit()
.attr("opacity", 1)
.transition().duration(200).attr("opacity", 0)
.remove();
So I have a problem I am using this code from bl.ocks.org, I have modified it a bit so far.
Now I am stuck with this problem: I want to format the ticks such that every Sunday appears read.
I thought this little piece of code below should do the trick for me, but apparently it does not.
I really believe it is a problem of me understanding, the call back function properly. So I would love to understand why if I call weekdays which works within the callback function, is not called by this little piece of code?
Which I have put after selectAll("text") in the big chunk of code below.
I would be very grateful if someone could help me out!
Thank you!
This is where I thought formatting should work in my code:
var xAxis = d3.svg.axis().scale(x)
.orient("bottom").ticks(31)
.tickFormat(d3.time.format("%d-%b")) // puts date on axis in
var timeaxis = svg.append("g") // Add the X Axis
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.selectAll("text") //selects axis labels
.style("color", (data, function(d) { return weekdays;
if (weekdays == 0) { //red if 0 (= Sunday)
return "red";
} else {
return "black";
}
}));
.attr("dx", "-.8em")
.attr("dy", ".35em")
.attr("transform", "translate(0,15) rotate(-70)")
This is how I read in the data:
// Get the data, works!
d3.csv("monthCPU.csv", function(error, data) {
data.forEach(function(d) {
d.date = parseDate(d.date);
var weekday = d.date.getDay();
d.CPUs = +d.CPUs;
d.Memory = +d.Memory;
});
This is the dummy data I am using in a CSV file called monthCPU.csv .
date,CPUs,Memory
2012-1-1,13,70
2012-1-2,50,13
2012-1-3,80,13
2012-1-4,5,13
2012-1-5,64,100
2012-1-6,13,13
2012-1-7,64,50
2012-1-8,30,10
2012-1-9,30,10
2012-1-10,13,13
2012-1-11,13,13
2012-1-12,13,13
2012-1-13,13,13
2012-1-14,13,13
2012-1-15,13,13
2012-1-16,13,13
2012-1-17,13,13
2012-1-18,13,33
2012-1-19,60,40
2012-1-20,1,11
2012-1-21,12,12
2012-1-22,13,13
2012-1-23,13,13
2012-1-24,1,11
2012-1-25,12,12
2012-1-26,13,13
2012-1-27,13,13
2012-1-28,1,11
2012-1-29,12,12
2012-1-30,13,13
2012-1-31,13,13
2012-1-30,13,13
2012-1-31,13,13
I'm using a range slider to add/remove points from a line which follows an histogram.
Without updating the data, the text labels are shown correctly, when I update the values with the slider I'd like that the text-labels follow the line, so they show the values of the points correctly.
The update for the line works, but I am not able to update the text labels correctly.
Right now the laberls are plotted like so outside of the on-change function:
var SerifText = svg.append("g")
.attr("id", "serif-labels")
.selectAll("text")
.data(histogSerif)
.enter()
.append("text")
.text(function (d) { return d.y >= 1 ? d.y : null; })
.attr("text-anchor", "middle")
.attr("font-size", 10 + "px")
.attr("fill", "#f0027f")
.attr("x", function (d) { return x(d.x)})
.attr("y", function (d) { return y(d.y) - padding; })
.attr("visibility", "hidden"); //the hidden & visible is managed by an external checkbox
And then on the on-change function I put this code:
SerifText.selectAll("text").remove();
/* If I do SerifText.remove() it does remove the text but also the group of course,
but as it is it doesn't work */
SerifText.selectAll("text")
.data(histogSerif)
.enter()
.append("text")
.text(function (d) { return d.y >= 1 ? d.y : null; })
.attr("text-anchor", "middle")
.attr("font-size", 10 + "px")
.attr("fill", "#f0027f")
.attr("x", function (d) { return x(d.x)})
.attr("y", function (d) { return y(d.y) - padding; });
But this doesn't work unfortunately. I'd like also to be able to retain the manage of the visible/hidden status by the external checkbox if possible.
Full code is here
Instead of trying to save a reference to the labels that you are going to delete, and then re-selecting them, how about just use a variable that references the SerifText element selection, e.g.:
var SerifTextGroup = svg.append("g")
.attr("id", "serif-labels");
SerifTextGroup.selectAll("text")
.data(histogSerif)
.enter()
//... and so on
Then the rest of your code should work as written (just change SerifText to SerifTextGroup).
I am trying to add labels to the bilevel sunburst / partition shown here - http://bl.ocks.org/mbostock/5944371:
I have added labels to the first 2 levels and rotated them correctly. But I cant now add the new level's labels during a transition. To get started I have put ...
text = text.data(partition.nodes(root).slice(1), function(d) { return d.key; });
straight after the ...
path = path.data(partition.nodes(root).slice(1), function(d) { return d.key; });
line but it throws the following error ...
Uncaught TypeError: Cannot read property '__data__' of undefined
What am I doing wrong?
I used bilevel partition to add labels via a mouseover, and found that in this version (unlike the other sunburst partition), there are two different sections where "path" is defined and updated. The first is here:
var path = svg.selectAll("path")
and then again below the transition you highlighted in your code:
path.enter().append("path")
When i added my mouseover labels, I needed to reference my text function in both places or it wouldn't work after transition.
If you can post your code to JSFiddle or bl.ocks.org we can try to play with it and see, but that was where I got tripped up at first.
Hope that helps.
*NOTE: Comment didn't post:
I'm sorry I'm not able to help more, but I'm also a newbie at D3. Here's where I got:
copy and paste your svg.selectAll("text") snippet after the second "path.enter().append("path") snippet. This will cause it to appear on subsequent zooms as well.
Problems I see: there's no "g" element so you need separate transitions for text as well or they all pile up. Also, I can't understand why they're positioned at their original partition spot instead of where the arc exists now.
My approach was add labels on mouseOver. I've uploaded that here: https://github.com/johnnymcnugget/d3-bilevelLabels
In this, I'm calling the two functions in both sets of "path" snippets, and the transitions are handled within the single variable of "legend"
Another D3 newbie, but I managed to resolve this. Beggining from the original Bilevel Partition:
Add the text for the first time the chart is drawn:
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);
var text = svg.selectAll("text")
.data(partition.nodes(root).slice(1))
.enter().append("text")
.attr("transform", function(d) { return "rotate(" + computeTextRotation(d) + ")"; })
.attr("x", function(d) {return radius / 3 * d.depth; })
.attr("dx", "6") // margin
.attr("dy", ".35em") // vertical-align
.html(function(d) { return d.name; });
when zooming in or out you have to redraw labels, add the following in function zoom(root, p)
text.enter().append("text")
.attr("transform", function(d) { return "rotate(" + computeTextRotation(d) + ")"; })
.attr("x", function(d) {return radius / 3 * d.depth; })
.attr("dx", "6") // margin
.attr("dy", ".35em") // vertical-align
.text(function(d) { return d.name; });
text.exit().transition()
.style("fill-opacity", function(d) { return d.depth === 1 + (root === p) ? 1 : 0; })
.remove();
text.transition()
.style("fill-opacity", 1)
.attr("transform", function(d) { return "rotate(" + computeTextRotation(d) + ")"; })
.attr("x", function(d) {return radius / 3 * d.depth; })
.attr("dx", "6") // margin
.attr("dy", ".35em") // vertical-align
Finally modify the computeTextRotation function as:
function computeTextRotation(d) {
return (d.x + (d.dx)/2) * 180 / Math.PI - 90;
}
Hope I haven't missed anything.
Indeed one modified line is missing in "2.". In function zoom(root, p), you should also add a line after path = path.data(partition ... .key; });:
path = path.data(partition.nodes(root).slice(1), function (d) {
return d.key;
});
text = text.data(partition.nodes(root).slice(1), function (d) {
return d.key;
});
Im trying to filter a dataset to only display labels for some select elements. The filter shown here seems to work, except it creates thousands of blank elements, which I obviously want to avoid. This is because the filter comes after the append, but if I move the filter above the append statement , it breaks.
What am i doing wrong here
var labels = svg.selectAll("text.label")
.data(partition.nodes(bp.data.preparedData))
.enter()
.append("text")
.filter(function(d){return d.ci_type === 'type'})
.attr("class", "label")
.attr("transform", function(d) { return "translate(" + arc.centroid(d) + ")"; })
.text(function(d, i) { return d.name } );
It sounds like you want to filter your data before passing it to D3. That is, your code would be
var labels = svg.selectAll("text.label")
.data(partition.nodes(bp.data.preparedData).filter(
function(d){return d.ci_type === 'type'}))
.enter()
.append("text")
.attr("class", "label")
.attr("transform", function(d) { return "translate(" + arc.centroid(d) + ")"; })
.text(function(d, i) { return d.name } );