I'm creating a simple bar chart and trying to make it respond to user clicks. The clicked bar is supposed to disappear. All seems to be working except clicking on the first bar makes a bar at the end disappear. I'm completely at a loss to why this is the case and would really appreciate any help.
Complete Code on Plunkr:
https://plnkr.co/edit/H8K0ISdhGrb5HirrX2MG?p=preview
I call the update function when a user clicks on a bar. I created a removefromarray function to return the data object minus data bound to the clicked bar. :
d3.tsv("CantTouchThis.tsv",function(d,i){
d.FieldGoals = +d.FieldGoals;
return d;
}, function(error,data){
if (error) throw error;
y.domain([0,d3.max(data, function(d){return d.FieldGoals})]);
x.domain(data.map(function(d){return d.Player}));data used as the x attribute
function update(indx){
var selection = g.selectAll(".bars")
.data(data.removefromarray(indx), function(d){console.log('d');console.log(d); return d}) //printing d shows the previous bars and new bars are being returned, I suspect this may be causing the problem, but not sure
selection.enter().append("rect")
.attr("class","bars")
.attr("width",function(d){return x.bandwidth()})
.attr("x",function(d){return x(d.Player)})
.attr("height",function(d){return height - y(d.FieldGoals)})
.attr("y",function(d){return y(d.FieldGoals)})
.on("click",function(d,i){update(i);});
console.log(selection.enter())
console.log(selection.exit())
selection.exit().remove()
}
Solution 1: You're missing an "update" selection:
selection.attr("width",function(d){return x.bandwidth()})
.attr("x",function(d){return x(d.Player)})
.attr("height",function(d){return height - y(d.FieldGoals)})
.attr("y",function(d){return y(d.FieldGoals)})
.on("click",function(d,i){update(i);});
Here is your updated plunker: https://plnkr.co/edit/qJY5KgY9FBvuJ8krddYm?p=preview
Solution 2: another very simple solution (that correctly addresses your question, "why doesn't first bar disappear using exit method?"): use a proper key in the data binding selection:
var selection = g.selectAll(".bars")
.data(data.removefromarray(indx), function(d){ return d.Player});
// this is the proper key function ---^
However, have in mind that this "solution" will not work for all clicks. That happens because your method for removing the data object (using splice in an extended prototype) is not working correctly.
Here is another updated plunker: https://plnkr.co/edit/rVTinxx45nmBvFiWwqgg?p=preview
PS: there are way easier ways to do what you want (and more adequate to a D3 code also).
Related
After making several bar charts using enter, update, exit method in D3js, I wanted to try the same with a pie chart. I thought I applied selections correctly, but the pie chart won't update with the new data in JSON. I looked for similar examples online, but couldn't find one which involved an .on("click" method. I want users to compare the lifespans of humans and animals using a donut chart. I'm trying to implement the search tool through the database of animals right now.
here's what a data object looks like for the query Goat:
[{"Animal":"Male","Life_Span":73},{"Animal":"Goat","Life_Span":10}]
I'm having trouble with this code in particular:
var pie = d3.pie()
.sort(null)
.value(function(d) { return d.Life_Span; });
//code for accessing data, etc
//enter remove selections
var path = svg.selectAll("path")
.data(pie(newdata))
var enterdata =
path.enter().append("path")
.attr("d",arc)
path.exit().remove()
enterdata.exit().remove()
I posted the full code on Plunkr here: http://plnkr.co/edit/3QSAPxQpju63tIXRd9p7?p=preview
A few weeks into learning d3js, I'm still struggling with enter,update exit selections even after reading many tutorials on the subject. I would really appreciate any help. Thanks
In D3 v4.x, you need to merge the update and enter selections:
var enterdata = path.enter()
.append("path")
.merge(path)
.attr("d",arc)
You don't need an exit selection because your data array has always 2 objects: except for the first time, your enter selection is always 0, and your exit selection is always 0.
I took the liberty of colouring the paths in different colours, according to their indices:
.attr("fill", (d,i) => i ? "teal" : "brown");
Here is your updated plunker: http://plnkr.co/edit/l6OzmxVfid9Cj7iv7IMg?p=preview
PS: You have some problems in your code, which I'll not change (here I'm only answering your main question), but I reckon you should think about them:
Don't mix jQuery and D3. You can do everything here without jQuery
Don't load all your CSV every time the user chooses an animal. It doesn't make sense. Instead of that, put the click function inside the d3.csv function (that way, you load the CSV only once).
I've been attempting to learn better visualization with node.js and the mapbox library.
Using this example here: Running Map Example
I'd like to add a graph of speed, and allow a user to click on a node, and see data about that position in a little popup - For today, I just want to get speed working.
It seems to be a recursive algorithm, so I need to implement variables to store the previous position and time, but I've ran into three problems:
I don't know how to use this date format: "2015-01-19T21:24:20Z" or Chroniton's parsing of it to generate a subtractable number to get the difference in time.
I don't know how to get the distance between two points using the code given, I could simply do sqrt((.x(point1) - .x(point2)) + (.y(point1) + .y(point2)), but I'm not sure how coordinates are stored or parsed in this example.
I don't know where to calculate the speed. It seems like the coordinates are only defined after the graphs are displayed, since the coordinates aren't used in the graphics. I am probably wrong, but I need some direction.
Here is what I have now:
Using the elevation display as my template, I think I have made it able to display the line by adding in the following three snippets:
Setting the scale:
var speed = d3.scale.linear()
.range([height, 0])
.domain([0, d3.max(dataRet, function(d) {
return d[1][2];
})]);
Adding in the line, with data:
var SpeedLine = d3.svg.line()
.x(function (d) { return x(d[0]); })
.y(function (d) { return speed(d[2])})
Displaying the line:
svg.append('path')
.datum(dataRet)
.attr('class', 'speed-line')
.attr('d', speedLine);
I know I have to add in a speed function similar to this psudocode:
var dt = chroniton.domain(Time1, Time2)
var speed[i] = LongLat(previousPoint).distanceto(currentPoint)/dt
And on the popup box:
dt.format(something to do with time formatting)
Note 1:, I changed the name of the function datePlaceHeart to dataRet since I'll be adding new things to do it, and datePlaceHeartSpeedStuffAndThings was getting a bit long ;)
Note 2: I haven't been able to start the pop-up because I haven't figured out how to calculate speed using the given data, and well, it seems kinda silly to do the easy one first. (With my luck, its actually not easy)
Please help? Here is my edited code in full (Edited index.js):
Code
This question builds on Lars Kotthoff's very helpful answer to the problem of transitioning with a D3 sunburst diagram that is based on different JSON data: d3 - sunburst - transition given updated data -- trying to animate, not snap
I've tried the final fiddle that Lars provided but when there is more than one transition, the animation still fails and we get snapping. The problem can be seen in this updated fiddle that contains a second transition.
From what I can tell, the x0 and dx0 values are not properly stored with the path object when calling the arcTweenUpdate function. When I check what the this object looks like inside arcTweenUpdate function, I get an [object SVGPathElement] at the beginning of the function when this.x0 and this.dx0 are read, and I get an [object Window] when the new values are written later. I'm relatively inexperienced with JS but that seemed like it could point to the problem.
Any help with addressing this and making the above fiddle work for multiple transitions (e.g. back and forth between the two JSONs) is highly appreciated. Thanks!
Well you've spotted a bug in my earlier answer :)
The problem is, as you say, that the saved values aren't updated properly. That's because this inside the callback doesn't refer to the path DOM element anymore. The fix is simple -- save a reference to this in the function at the level above and use that reference:
function arcTweenUpdate(a) {
var i = d3.interpolate({x: this.x0, dx: this.dx0}, a);
var that = this;
return function(t) {
var b = i(t);
that.x0 = b.x;
that.dx0 = b.dx;
return arc(b);
};
}
Complete demo here.
First of all, let me be clear on this: i'm a d3 newbie!
So, i have a chart based on utc time domain. the domain is one of this: 1 day | 3 days, and I can switch from one to the other with a user action.
Refreshing / transitioning the x to represent 24hs to 72hs is working properly, but I'm having difficulties to update the elements that are already in the chart. Some how it seems that after changing the domain .data() .enter() won't actually enter (so my attr don't get updated).
This is a jsbin with the full example of my problem. Any clues?
You need to follow this series of examples very carefully.
Here is the problem code corrected ...
group.enter()
.append('g')
.attr('class', 'flight');
group.attr('transform', function (trip) {
return 'translate(' + hourDomain( Math.min.apply(null, _.map(trip.legs, 'departureDateTime'))) + ', 60)';
});
The problem is that you are applying the positioning to the enter selection only: only new nodes. After the first call, the enter selection is always empty because there are no new nodes. The linked examples will explain it.
I have a simple D3 scatterplot that I switch among displaying several different attributes of my data, but while I can get the data points to change (and to transition as I want them to), and can change the labels to the figure's axes, I cannot get the axes themselves to update (let alone transition).
I suspect I'm doing something in the wrong order, or am missing a step, but I can't figure out from the documentation or examples I'm working from what I'm missing.
How do I get my axes to update along with my data?
The mystery arises from the behavior at the end of the linked code:
d3.select("#distancefig").on("click", function () {
d3.event.preventDefault();
updatePlot('distancefig', false);
});
d3.select("#speedfig").on("click", function () {
d3.event.preventDefault();
updatePlot('speedfig', false);
});
d3.select("#distspeedfig").on("click", function () {
d3.event.preventDefault();
updatePlot('distspeedfig', false);
});
updatePlot('distancefig', true);
Here the final, explicit updatePlot updates everything as expected (and changing the argument changes everything — axes, labels, points — as it should), but the calls invoked by clicking on the links change only the data points and labels; they do not update the axes.
I'm not familiar with how you structured your code, but I would basically put everything that happens with the database inside the d3.csv callback function, so the final part, regarding the functionality of the text, would have the update of the x and y axis with the updated domain, like:
d3.csv{
//select the text then add the onclick event
.on("click" function () {
x.domain(d3.extent(dataset, function (d) { return /* your updated value here */); })).nice();
//select the x-axis and then add this:
.transition()
.duration(1500)
.call(xAxis);
//then do the same for the y axis
};}
The critical step is to make sure that you select the axes correctly.
In each of the click handlers you are passing "false" as the 2nd argument. In the last statement, you are passing "true". Could this be the cause?