D3js path update enter and exit - javascript

What am I missing?
I am allowing users to remove and plot their own data point. My line path is drawn with this and it works fine.
let self = this;
let line = D3['line']()
.x(function (d) { return self.getColX(d.x); })
.y(function (d) { return self.getRowY(d.y); });
this.group = this.canvas.append("g")
.attr("transform", "translate(25,25)");
//bind the data
this.group.selectAll("path")
.data(this.data[this.site].drawLine)
.enter()
.append("path")
.attr("d", line)
.attr("fill", "none")
.attr("stroke", "black");
this.group.selectAll('path').exit().remove()
My problem is, if I pop the last coordinates out and add a new one, call the draw function, the new points gets added correctly but the old points doesn't get remove.
For example: my line goes from (x,y): (1,3) to (3,6) to (7,8), if i remove (7,8) and replace that with 5,6. i will see a new line from (3,6) to (5,6) but the line from (3,6) to (7,8) which is no longer in the data array still there.

The enter() and exit() selections are created after D3 compares your selection with the data provided. So they are available in the return of these calls:
this.group.selectAll("path")
.data(this.data[this.site].drawLine)
And that's why new data is appended, enter() works just fine.
With this.group.selectAll('path').exit().remove() you create a new selection but is not comparing the selection against any data, therefore enter() and exit() selections aren't available to work with.
Long story short, just apply .exit().remove() to your initial selection and it should work. Something like this:
let update = this.group.selectAll("path")
.data(this.data[this.site].drawLine)
update.enter()
.append("path")
.attr("d", line)
.attr("fill", "none")
.attr("stroke", "black")
update.exit()
.remove()

Related

[Help]: Multi Line with d3.nest() not rendering lines

I am trying to render multiple lines with x, y axis as 1st, 2nd values of each array out of 5 arrays within datarow dataset. i want to generate 3 different lines each for 0, 15, 1007 values of 3rd elements in all 5 arrays by nesting through each of the 3rd element in the datarow dataset.
What i tried: https://jsfiddle.net/data_x/23143n3r/
I followed http://bl.ocks.org/d3noob/d8be922a10cb0b148cd5.
The important code:
var line = d3.svg.line()
.x(function(d,i){return x(datarow[i][0]);})
.y(function(d,i){return y(datarow[i][1]);})
.interpolate("linear");
//Nest the entries
var nest = d3.nest()
.key( function(d) { return d[2]})
.entries(datarow);
//Loop through each d[2]
nest.forEach(
function(d,i)
{
svg.append("path")
.attr("class", "line")
.attr("d", line(d.values[0][2]))
.style("stroke-width", 1)
.style("stroke", "steelblue")
.style("fill", "none")
}
);
In above code, d.values[0] returns all the array elements. As i want to plot multiple lines for 3rd values in each array, i am returning 3rd element in each array. But, i am still not able to figure out why nothing is being rendered.
Any help or hints are appreciated,
Thank you,
With the help of a friend,
Problem is at
`var line = d3.svg.line()
.x(function(d,i){return x(datarow[i][0]);})
.y(function(d,i){return y(datarow[i][1]);})
.interpolate("linear");`
whereas it should be like
d3.svg.line()
.x(function(d){return x(d[0]);})
.y(function(d){return y(d[1]);})
.interpolate("linear");`
Line x,y accesory functions takes array of values as input not single values.

d3.js - Multiple line chart doesn't update circles

I'm working on a d3 chart (Multiple line chart).
I'm trying to represent a stock prediction, so basically the chart contains two lines: stock values line and an other one for my prediction.
The prediction is monthly, all days of month are represented in the chart.
In order to choose the month I have added a dropdown menu.
I appended a circle on each daily data, and works well for the first time. When user tries to change the month, the old circles are not updated, but the new ones are added.
Follow the code about circles:
topicEnter.append("g").selectAll("circle")
.data(function(d){return d.values})
.enter()
.append("circle")
.attr("r", 5)
.attr("cx", function(dd){return x(dd.date)})
.attr("cy", function(dd){return y(dd.probability)})
.attr("fill", "none")
.attr("stroke", "black");
I have done a fiddle to understand better the situation and in order to show code.
What am I missing here? Why don't the circles update themself with the lines?
To solve the issue about circles not updating you can do the following:
function update(topics) {
// Calculate min and max values with arrow functions
const minValue = d3.min(topics, t => d3.min(t.values, v => v.probability));
const maxValue = d3.max(topics, t => d3.max(t.values, v => v.probability));
y.domain([minValue, maxValue]);
x2.domain(x.domain());
y2.domain(y.domain());
// update axes
d3.transition(svg).select('.y.axis').call(yAxis);
d3.transition(svg).select('.x.axis').call(xAxis);
// Update context
var contextUpdate = context.selectAll(".topic").data(topics);
contextUpdate.exit().remove();
contextUpdate.select('path')
.transition().duration(600)
.call(drawCtxPath);
contextUpdate.enter().append('g') // append new topics
.attr('class', 'topic')
.append('path').call(drawCtxPath);
// New data join
var focusUpdate = focus.selectAll('.topic').data(topics);
// Remove extra topics not found in data
focusUpdate.exit().remove(); //remove topics
// Update paths
focusUpdate.select('path')
.transition().duration(600)
.call(drawPath)
// Update circles
var circlesUpdate = focusUpdate
.selectAll('.topic-circle')
.data(d => d.values);
circlesUpdate.exit().remove();
circlesUpdate.transition().duration(600).call(drawCircle);
circlesUpdate.enter().append('circle').call(drawCircle);
// Add new topics
var newTopics = focusUpdate.enter().append('g') // append new topics
.attr('class', 'topic');
// Add new paths
newTopics.append('path').call(drawPath)
// Add new circles
newTopics.selectAll('.topic-circle')
.data(d => d.values)
.enter()
.append('circle')
.call(drawCircle);
}
With these helper functions to reduce code duplication:
function drawCtxPath(path) {
path.attr("d", d => line2(d.values))
.style("stroke", d => color(d.name));
}
function drawPath(path) {
path.attr("d", d => line(d.values))
.attr('clip-path', 'url(#clip)')
.style("stroke", d => color(d.name));
}
function drawCircle(circle) {
circle.attr('class', 'topic-circle')
.attr('clip-path', 'url(#clip)')
.attr("r", d => 5)
.attr("cx", d => x(d.date))
.attr("cy", d => y(d.probability))
.attr("fill", "none")
.attr("stroke", "black");
}
I think there are some additional issues in your code, when you select the same month twice you get an error, we can fix that by doing the following:
d3.select('#month_chart').on("change", function() {
// Get selected value of the select
var month = this.options[this.selectedIndex].value;
// Since you have hardcoded data we need to return a new array
// This is why if you select the same month twice your code breaks
// since parseDate will fail since the data will be already parsed
// the second time
var monthData = get_monthly_data(month).map(d => {
return {
date: parseDate(d.date),
predicted_bool: d.predicted_bool,
target: d.target
};
});
// Lets use arrow functions!
var keys = d3.keys(monthData[0]).filter(k => k !== 'date');
color.domain(keys);
// More arrow functions!
var topics = keys.map(key => {
return {
name: key,
values: monthData.map(d => {
return {
date: d.date,
probability: +d[key]
};
})
};
});
x.domain(d3.extent(monthData, d => d.date));
update(topics);
});
// A good ol' switch
function get_monthly_data(month) {
switch (month) {
case 'gennaio':
return data_1;
case 'febbraio':
return data_2;
case 'marzo':
return data_3;
default:
return data_1;
}
}
Working jsfiddle:
https://jsfiddle.net/g699scgt/37/
The problem is your update cycle, but there are a good number of examples of the enter, update, exit process in d3.
But essentially:
You append a new g element for each batch of circles, which means you have an empty selection (no circles are in that g yet) each time and each data point is appended (and none are removed). You don't need this extra append. Take a look at the DOM structure on each append in your existing code.
Your enter() selection returns new elements - not modified elements. So if your total number of elements remains the same you will have an empty enter() selection. You'll want to update existing elements separately (alternatively, remove them all and append them all every time).
You'll want something closer to this:
// set the data
circles = topic.selectAll("circle")
.data(function(d){return d.values});
// update existing circles
circles.attr("cx", function(dd){return x(dd.date)})
.attr("cy", function(dd){return y(dd.probability)});
// add new circles
circles.enter()
.append("circle")
.attr("r", 5)
.attr("cx", function(dd){return x(dd.date)})
.attr("cy", function(dd){return y(dd.probability)})
.attr("fill", "none")
.attr("stroke", "black");
// remove excess circles
circles.exit().remove();
You'll likely also want to revise the lines that append the lines to reflect the enter, update, exit cycle in d3.

D3.js exit().remove() does not remove data/line

I hava a multi line chart with D3.js.
Initial rendering works just fine. When I try to update, only new lines are added instead of the old ones update/removed.
Example: http://jsfiddle.net/ty192n93/6/
This is the part where the line is rendered:
var node = svg.selectAll(".g.city")
.data(data, function(d) { return d.name; });
var enter = node.enter().append("g")
.attr("class", "city");
enter.append("path")
.attr("class", "line")
.attr("d", function(d) {
return line(d.data);
})
.style("stroke", function(d) {
return color(d.name);
});
// Text element left out
var remove = node.exit().remove();
How is it possible to update the old values (key function is specified) or remove them completely?
Your first selection is wrong. The selector .g.city selects all elements having both classes g and city. Your selection is valid, though, but will always return an empty selection putting all your data in the enter selection and leaving the exit selection empty. Instead, you are interested in group elements g having class city. Removing the first dot from the selector should do the trick:
var node = svg.selectAll("g.city") // <-- remove the dot
.data(data, function(d) { return d.name; });

Doughnut graph is not working in d3 with two Arc object dynamically

I'm trying to create a doughnut graph, I have two entities to represent on the dougnut graph and need to show one with thick line i.e wider arc and one with narrow arc.
I tried to achieve like this. Here is the FIDDLE
I made two arc in beginning arc and arc2 and in the function
var slice_path = slice_group.append("path")
.attr("fill", function(d, i) { return color(i); })
.attr("d", function(d,i){console.log(d); return arc;})
.each(function(d) { this._current = d; })
here in the third line I want to do something like this
.attr("d", function(d,i){
if(based on condition in d.series)
return arc;
else
return arc2;
})
But I'm not able to make it work, If i write it as
.attr("d", function(d,i){console.log(d); return arc;})
It do not work ,but if i write as
.attr("d", arc)
It works.
Could you guide me in case... !..

Why can't I create a group of circles with a dataset more than once in d3?

m creating a scatter plot and I have problems plotting my circles (datapoints).
When appending the first set of circles it is drawn fine on the graph. However the next set I try to append won't be draw for some reason. How can this be? Am I using the Enter/append/select wrong the 2nd time?
I have a JSFiddle with my code: http://jsfiddle.net/4wptM/
I have uncommented the parts where I load and manipulate my data and created the same array with a smaller sample. As it can be seen only the first set of circles are shown and the 2nd ones aren't.
My code of the circle section is pasted below:
var circle = SVGbody
.selectAll("circle")
.data(graphData[0])
.enter()
.append("circle")
.attr("cx",function(d){return xScale(100);})
.attr("cy",function(d){return yScale(parseFloat(d))})
.attr("r",5);
circle = SVGbody
.selectAll("circle")
.data(graphData[1])
.enter()
.append("circle")
.attr("cx",function(d){return xScale(200);})
.attr("cy",function(d){return yScale(parseFloat(d))})
.attr("r",5);
I just have those 2 copied to try and figure out the problem. The actual code I have is in the following for loop. When I run it in this loop it draws the circles for when the index is 1 and a few of when the index is 3.
for(var i = 0;i < graphData.length;i++){
var circle = SVGbody
.selectAll("circle")
.data(graphData[i])
.enter()
.append("circle")
.attr("cx",function(d){return xScale((i*100)+100);})
.attr("cy",function(d){return yScale(parseFloat(d))})
.attr("r",20);
//console.log(i + " ::::::: " + graphData[i])
}
Some help would be so greatly appretiated. I really can't figure out what I'm doing wrong.
When operating on a existing set of elements (as it is the case here on the second .selectAll("circle") the .data() method uses only these elements from the array which have no corresponding index in the selecton made with .selectAll(). To prevent this behaviour you will have to add a key function as the second parameter of .data() as explained here and here.
Here this function only has to return each element of the array:
function (d) {
return d;
}
In the fiddle it would look something like this:
var circle = SVGbody
.selectAll("circle")
.data(graphData[0], function(d) { return d; }) // <-- this one
.enter()
.append("circle")
.attr("cx",function(d){return xScale(100);})
.attr("cy",function(d){return yScale(parseFloat(d))})
.attr("r",5);
fiddle
And to change the fill color of an element you have to use .style("fill", ...) and not .attr("fill", ...)
.style("fill", "green")

Categories