d3: update data appended on elements - javascript

I have a graph with a line, two areas and a few circles in it:
I wrote an update method, which works just fine for everything except the circles:
function updateGraph() {
xScale.domain([startDate.getTime(), endDate.getTime()]);
addTimedTickPadding(xAxis);
yScale.domain([0, d3.max(interpolatedData, function (d) {
return d.y0 + d.y1;
})]);
var updateGroup = parentGroup.transition();
updateGroup.select('.xaxis')
.duration(750)
.call(xAxis);
updateGroup.select('.yxaxis')
.duration(750)
.call(yAxis);
updateGroup.select('.xgrid')
.duration(750)
.call(verticalGridLine());
updateGroup.select('.area-top')
.duration(750)
.attr('d', areaTop(interpolatedData));
updateGroup.select('.area-bottom')
.duration(750)
.attr('d', areaBottom(interpolatedData));
updateGroup.select('.line')
.duration(750)
.attr('d', line(interpolatedData));
updateGroup.select('.post')
.duration(750)
.data(interpolatedData)
.attr('cx', function (d) {
return xScale(dateParser.parse(d.x))
})
.attr('cy', function (d) {
return yScale(d.y0 + d.y1)
});
}
Everything transitions as expected but the circles. They just stay in place. My thought was: I append the updated data to each element and alter the cx and cy attributes accordingly, so the circles transition to their new place. But they don't move :( What am I doing wrong here?
Here is the initial code for the circles:
dataGroup
.selectAll('.post')
.data(interpolatedData)
.enter()
.append('circle')
.classed('post', true)
.attr({
'r': circleRadius,
'fill': circleFillColor,
'stroke-width': circleStrokeWidth,
'stroke': circleStrokeColor,
'cx': (function (d) {
return xScale(dateParser.parse(d.x))
}),
'cy': (function (d) {
return yScale(d.y0 + d.y1)
})
});
PS: I read abaout enter() and exit() here but don't really know, how they could be useful for updating the data which is appended on the elements.
PPS: I tried selectAll() too, but it doesn't seem to have a data() function.

Related

Error: <path> attribute d: Expected arc flag ('0' or '1')

I have looked for possible solutions but nothing worked for me.
Problem is when I try to update the data and the pie chart accordingly, the transition does not work and prints error, mentioned in the topic, more than once. I am kinda new to JS, so I am looking for some help.
Code:
var pie = d3.pie();
var pathArc = d3.arc()
.innerRadius(200)
.outerRadius(250);
var color = d3.scaleOrdinal(d3.schemeCategory10);
var t = d3.transition()
.duration(500);
var path = piesvg.selectAll("path")
.data(pie(gdp_values));
path.exit()
.transition(t)
.remove();
path.transition(t)
.attr("d",function (d) {
return pathArc(d);
})
.attr("fill",function(d, i){return color(i);});
path.enter()
.append("path")
.transition(t)
.attr("d",pathArc)
.attr("fill",function(d, i){return color(i);});
Initial dataset(gdp_values);
[407500000000, 417300000000, 439800000000, 680900000000, 980900000000, 1160000000000, 1727000000000, 2249000000000, 2389000000000, 3074000000000]
It does work when data changed to the another similar data, however when changes to the data as follows, transitions doesnot work and throws the same error 40 times.
[7714000000, 123900000000, 846200000000]
Any thoughts?
You have to invert the order of your selections: the enter selection should come before the update selection:
path.enter()
.append("path")
.transition(t)
.attr("d", pathArc)
.attr("fill", function(d, i) {
return color(i);
});
path.transition(t)
.attr("d", function(d) {
return pathArc(d);
})
.attr("fill", function(d, i) {
return color(i);
});
Here is the demo:
var piesvg = d3.select("svg").append("g").attr("transform", "translate(250,250)")
var gdp_values = [407500000000, 417300000000, 439800000000, 680900000000, 980900000000, 1160000000000, 1727000000000, 2249000000000, 2389000000000, 3074000000000];
var gdp_values2 = [7714000000, 123900000000, 846200000000];
var pie = d3.pie();
var pathArc = d3.arc()
.innerRadius(200)
.outerRadius(250);
var color = d3.scaleOrdinal(d3.schemeCategory10);
var t = d3.transition()
.duration(500);
update(gdp_values)
setTimeout(function() {
update(gdp_values2);
}, 1000)
function update(data) {
var path = piesvg.selectAll("path")
.data(pie(data));
path.exit()
.transition(t)
.remove();
path.enter()
.append("path")
.transition(t)
.attr("d", pathArc)
.attr("fill", function(d, i) {
return color(i);
});
path.transition(t)
.attr("d", function(d) {
return pathArc(d);
})
.attr("fill", function(d, i) {
return color(i);
});
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg width="500" height="500"></svg>
I got this error when making data updates to a chart. Solution in my case was to prevent drawing of the chart during the time the data was loading. Guessing the arcTween code doesn't handle data changes gracefully.

D3JS Scatter plots refresh speed

I was wondering if there is a way to change the Scatter plots refresh speed?
As you can see in this link the scatter plots gets updated but the time gap between the appearance and disappearance is unreasonable, it look like they are flashing dots.... I tried moving the circle.remove() function right above the circle.transition but it makes no difference.
Below is the relevant code of the refresh function. Thanks!
function updateData() {
// Get the data again
data = d3.json("2301data.php", function(error, data) {
data.forEach(function(d) {
d.dtg = parseDate(d.dtg);
d.temperature = +d.temperature;
// d.hum = +d.hum; // Addon 9 part 3
});
// Scale the range of the data again
x.domain(d3.extent(data, function(d) { return d.dtg; }));
y.domain([0, 60]);
var svg = d3.select("#chart1").select("svg").select("g");
svg.select(".x.axis") // change the x axis
.transition()
.duration(750)
.call(xAxis);
svg.select(".y.axis") // change the y axis
.transition()
.duration(750)
.call(yAxis);
svg.select(".line") // change the line
.transition()
.duration(750)
.attr("d", valueline(data));
var circle = svg.selectAll("circle").data(data);
circle.remove() //remove old dots
// enter new circles
circle.enter()
.append("circle")
.filter(function(d) { return d.temperature > 35 })
.style("fill", "red")
.attr("r", 3.5)
.attr("cx", function(d) { return x(d.dtg); })
.attr("cy", function(d) { return y(d.temperature); })
// Tooltip stuff after this
.on("mouseover", function(d) {
div.transition()
.duration(500)
.style("opacity", 0);
div.transition()
.duration(200)
.style("opacity", .9);
div .html(
d.temperature + "C" + "<br>" +
formatTime(d.dtg))
.style("left", (d3.event.pageX + 8) + "px")
.style("top", (d3.event.pageY - 18) + "px");})
.on("mouseout", function(d) {
div.transition()
.duration(500)
.style("opacity", 0);
});
circle.transition().attr("cx", function(d) { return x(d.dtg); });
// exit
circle.exit();
});
}
Looking at your example as it runs, you appear to have loads more circles in the dom than are visible. This is because you add circles for all the data, but then only give positions to those that meet the filter criteria you set.
There was a related question the other day about data filtering versus d3 filtering - Filtering data to conditionally render elements . Use data filtering if you don't want to add something full stop, use d3.filter if you want to isolate some elements for special treatment (transitions, different styling etc).
At the moment you're filtering the d3 selection once all the circles are added, but in your case I'd suggest filtering the data before it gets to that stage is best (and as suggested by others in that other question). This may make it run faster (but you're also at the mercy of db updates by the look of your example?)
data = data.filter (function(d) { return d.temperature > 35; }); // do filtering here
var circle = svg.selectAll("circle").data(data);
circle.exit().remove() //remove old dots
// enter new circles
circle.enter()
.append("circle")
.style("fill", "red")
.attr("r", 3.5)
.attr("cx", function(d) { return x(d.dtg); })
.attr("cy", function(d) { return y(d.temperature); })
...
PS. It's a bit confusing what you're trying to do with the circle.remove() and circle.exit(). circle.remove() will remove all existing circles (even ones that exist and have new data), circle.exit() at the end will then have no effect. I'd just have circle.exit().remove() to replace the two calls you make.
Also, without a key function - https://bost.ocks.org/mike/constancy/ - on your .data() call, you may find dots move around a bit. If your data points have ids, use them.
var circle = svg.selectAll("circle").data(data, function(d) { return d.id; /* or d.dtg+" "+d.temperature; if no id property */});
Thanks to mgraham the problem was solved.! Below is the revised code in case someone else needs it.
function updateData() {
// Get the data again
data = d3.json("data.php", function(error, data) {
data.forEach(function(d) {
d.dtg = parseDate(d.dtg);
d.temperature = +d.temperature;
});
// Scale the range of the data again
x.domain(d3.extent(data, function(d) { return d.dtg; }));
y.domain([0, 60]); // Addon 9 part 4
var svg = d3.select("#chart1").select("svg").select("g");
svg.select(".x.axis") // change the x axis
.transition()
.duration(750)
.call(xAxis);
svg.select(".y.axis") // change the y axis
.transition()
.duration(750)
.call(yAxis);
svg.select(".line") // change the line
.transition()
.duration(750)
.attr("d", valueline(data));
data = data.filter (function(d) { return d.temperature > 35; });
var circle = svg.selectAll("circle").data(data, function(d) { return d.dtg+" "+d.temperature;});
circle.exit().remove() //remove old dots
// enter new circles
circle.enter()
.append("circle")
.style("fill", "red")
.attr("r", 3.5)
.attr("cx", function(d) { return x(d.dtg); })
.attr("cy", function(d) { return y(d.temperature); })
// Tooltip stuff after this
.on("mouseover", function(d) {
div.transition()
.duration(500)
.style("opacity", 0);
div.transition()
.duration(200)
.style("opacity", .9);
div .html(
d.temperature + "C" + "<br>" +
formatTime(d.dtg))
.style("left", (d3.event.pageX + 8) + "px")
.style("top", (d3.event.pageY - 18) + "px");})
.on("mouseout", function(d) {
div.transition()
.duration(500)
.style("opacity", 0);
});
circle.transition().attr("cx", function(d) { return x(d.dtg); });
});
}
</script>

Why are my coordinates undefined only inside my circle definition?

I have a set of circles defined as
nodes = [{
x: xRange(xvalue),
y: yRange(getY(xvalue)),
...
}]
vis.selectAll(".nodes")
.data(nodes)
.enter().append("circle")
.attr("class", "nodes")
.attr("cx", function (d) {
return d.x;
(coordinate display)
})
.attr("cy", function (d) {
return d.y;
})
.attr("r", "7px")
.attr("fill", "black")
.attr("transform", function (p) {
return "translate(" + p.x + "," + p.y + ")";
})
The problem that I am having with these circles is that the coordinates taken from nodes are undefined when the circles are being defined, despite being defined everywhere else. Here is a test case to represent this problem, where the coordinates of one of the dots should be displayed, but isn't, since it seems to be undefined. To prove that the axes work, I have placed a dot in the first quadrant of the graph. Is there any reason for why this is happening?
You're returning before you run the logic :
.attr("cx", function (d) {
return d.x;
(coordinate display)
})
change to :
.attr("cx", function (d) {
(coordinate display)
return d.x;
})

Append element on d3 dragstart

I'm trying to use d3 to make a circle that is draggable, but leaves a copy of the original in place. Here is the circle:
g.selectAll('circle')
.data([{
cx: 90,
cy: 80,
r: 30 }])
.enter().append('circle')
.attr('cx', function (d) {return d.cx})
.attr('cy', function (d) {return d.cy})
.attr('r', function(d) {return d.r})
.attr('class','original')
.call(dragOriginal);
Here is the drag behavior:
var dragOriginal = d3.behavior.drag()
.on('dragstart', cloneSpeciesMaker)
.on('drag', function (d, i) {
d.cx += d3.event.dx;
d.cy += d3.event.dy;
d3.select(this).attr('cx', d.cx).attr('cy', d.cy)
});
And here is the dragstart function:
function cloneSpeciesMaker(d) {
var svg = d3.select('svg');
//original becomes copy
d3.select(this)
.classed('original',false)
.attr('class','copy');
// creates new 'original' in place
var data = [{cx:d.cx,cy:d.cy,r:d.r}];
svg.append('circle')
.data(data)
.attr('class','original')
.attr("cx",function(d) {return d.x})
.attr("cy",function(d) {return d.y})
.attr("r",function(d) {return d.r})
.style("fill","purple")
.attr("class","original")
.call(dragOriginal);
}
Right now, I'm succeeding in making the original circle become a 'copy' and dragging it around, but it the part where I append a new circle in its old place isn't working, can anyone explain why?
One problem I can see from the code is in this section:
function cloneSpeciesMaker(d) {
var svg = d3.select('svg');
//original becomes copy
d3.select(this)
.classed('original',false)
.attr('class','copy');
// creates new 'original' in place
var data = [{cx:d.cx,cy:d.cy,r:d.r}];
svg.append('circle')
.data(data)
.attr('class','original')
.attr("cx",function(d) {return d.x})
.attr("cy",function(d) {return d.y})
.attr("r",function(d) {return d.r})
.style("fill","purple")
.attr("class","original")
.call(dragOriginal);
}
You are setting the data like this
var data = [{cx:d.cx,cy:d.cy,r:d.r}];
But you are doing which is incorrect d.x and d.y is not defined by you in data.
.attr("cx",function(d) {return d.x})
.attr("cy",function(d) {return d.y})
This should have been:
.attr("cx",function(d) {return d.cx})
.attr("cy",function(d) {return d.cy})

Points on a map with D3.js

I have a geoJSON of zip code centroid points that I am plotting on a D3.js map. I can get them to display but I am unable to adjust the size of the points. I was assuming that .attr("r", 1) would be doing that but I must be missing something.
d3.json("ZipPoints.json", function (zipPoints) {
svg.selectAll("g")
.data(zipPoints.features)
.enter()
.append("path")
.attr("d", path)
.attr("r", 1)
.style("fill", "red");
});
EDIT:
d3.json("ZipPoints.json", function (zipPoints) {
points.selectAll("circle")
.data(zipPoints.features)
.enter()
.append("circle")
.attr("r", 1.5)
.attr("transform", function(d, i) {
return "translate(" + projection(zipPoints.features[i].geometry.coordinates) + ")";
})
.style("fill", "red")
.classed("point", true);
});
You could try the following.
var pins = svg.append("g");
d3.json("ZipPoints.json", function(zipPoints) {
pins.selectAll("circle")
.data(zipPoints.features)
.enter()
.append("circle")
.attr("r", 5)
.style("fill", "red")
.classed("pin", true);
});
You may need transformation on these points to render them correctly (I guess).
In that case you could use the following bit of code. (The transformation function I used was required to plot data that had lat, long information on a map built using a specific projection).
.attr("transform", function(d) {
/*whatever transformation that needs to be done*/
return "translate(" + projection([ d.lon, d.lat ]) + ")";
})

Categories