Add points to D3 chart - javascript

I was trying to add points to this chart http://bl.ocks.org/nsonnad/4175202
countryEnter.selectAll("dot")
.data(data)
.enter().append("circle")
.attr("r", 3.5)
.attr("cx", function(d) { return x(d.year); })
.attr("cy", function(d) { return y(d.name); });
But it is didn't work https://plnkr.co/edit/ADuZkJQrq7mjZqDZwrBe
May be someone can help?

When working with a nested selection, your data call can return a part of the data. In this case the values array:
countryEnter.selectAll("dot")
.data(function(d){
return d.values; //<-- return just the values of your larger data-binding
})
.enter().append("circle")
.attr("r", 3.5)
.attr("cx", function(d) { return x(d.year); })
.attr("cy", function(d) { return y(d.stat); });
Updated code.

Related

how to move circles(data points) in d3.js

I have 2 lines in the form of waves plotted in x-y axis based on randomly generated data and i am showing circles on the waves denoting the data points on it.
Based on setInterval of 200 ms, I am updating the original data and the lines(waves) are moving to the left, but the issue is that the only circles which are there in the initial interval are moving and for 2nd interval onward the circles are not showing up on the waves.
see the jsfiddle for the running code : https://jsfiddle.net/rajatmehta/tm5166e1/10/
here is the code :
chartBody.append("path") // Add the valueline path
.datum(globalData)
.attr("id", "path1")
.attr("class", "line")
.attr("d", valueline);
chartBody.selectAll(null)
.data(globalData)
.enter()
.append("circle")
.attr("class", "dot1")
.attr("r", 3)
.attr("cx", function(d) {
console.log(d);
return x(d.timestamp);
})
.attr("cy", function(d) {
return y(d.value);
});
chartBody.selectAll(null)
.data(globalDataNew)
.enter()
.append("circle")
.attr("class", "dot2")
.attr("r", 3)
.attr("cx", function(d) {
return x(d.timestamp);
})
.attr("cy", function(d) {
return y(d.value);
});
chartBody.append("path") // Add the valueline path
.datum(globalDataNew)
.attr("id", "path2")
.attr("class", "line")
.attr("d", valueline2);
any idea how to do that ?
You need to create new circles based on the updated data. Currently, you are only updating the data to selection, but not appending circles, and then moving existing circles to the left.
For example, you could to this:
chartBody.selectAll(".dot1")
.data(globalData, function(d){ return d.timestamp; })
.enter()
.append("circle")
.attr("class", "dot1")
.attr("r", 3)
.attr("cx", function(d) {
return x(d.timestamp);
})
.attr("cy", function(d) {
return y(d.value);
});
chartBody.selectAll(".dot2")
.data(globalDataNew, function(d){ return d.timestamp; })
.enter()
.append("circle")
.attr("class", "dot2")
.attr("r", 3)
.attr("cx", function(d) {
return x(d.timestamp);
})
.attr("cy", function(d) {
return y(d.value);
});
d3.selectAll(".dot1")
//.data(globalData)
.transition()
.duration(duration)
.ease("linear")
.attr("transform", "translate(" + String(dx) + ")");
d3.selectAll(".dot2")
//.data(globalDataNew)
.transition()
.ease("linear")
.duration(duration)
.attr("transform", "translate(" + String(dx) + ")");
See here: https://jsfiddle.net/tm5166e1/11/
This appends the data, using the timestamp as a key so you only create new circles for newly added datums.
(There is an issue when they are first added which is beyond the scope of this question, but it will be worth checking out these examples: https://bl.ocks.org/tomshanley/15a2b09a95ccaf338650e50fd207fcbf and https://bl.ocks.org/mbostock/1642874)

D3 access nested data

I have some nested data in this format:
[{"key":"PFOA",
"values":[
{"sampleDate":"2016-0126T05:00:00.000Z",
"shortName":"PFOA",
"pfcLevel":0,
"chemID":1},
{"sampleDate":"2016-01-19T05:00:00.000Z",
"shortName":"PFOA",
"pfcLevel":0,
"chemID":1},
{"sampleDate":"2016-01-12T05:00:00.000Z",
"shortName":"PFOA",
"pfcLevel":0,
"chemID":1}
],
"visible":0}
]
I'm trying to use this data to add circles to a multi-line graph. I can do this if I use the raw, non-nested data directly from the database, but that is causing other issues. I'd rather use the same nested data for the lines and the circles if possible. The nest function and the circle code is below:
var nested_data = d3.nest()
.key(function(d) { return d.shortName; })
.entries(data);
var circles = svg.selectAll(".circle")
.data(nested_data)
.enter().append("g")
.attr("class", "circle");
circles.append("circle")
.attr("stroke", function(d) { return color(d.key); })
.attr("fill", "white")
.attr("cx", function(d, i) { return x(d.values['sampleDate']) })
.attr("cy", function(d, i) { return y(d.values['pfcLevel']) })
.attr("r", 2);
I've tried different things like d.values[sampleDate] or .data(nested_data.values) but I am getting undefined errors on all of them.
Thanks in advance.
You are looking for a Nested Selection:
var nested_data = d3.nest()
.key(function(d) {
return d.shortName;
})
.entries(data);
var groups = svg.selectAll(".circle")
.data(nested_data)
.enter().append("g")
.attr("class", "circle");
var circles = groups.selectAll("circle") // start a nested selection
.data(function(d) {
return d.values; // tell d3 where the children are
})
.enter().append("circle")
.attr("stroke", function(d) {
return color(d.shortName);
})
.attr("fill", "white")
.attr("cx", function(d, i) {
return x(d.sampleDate) // use the fields directly; no reference to "values"
})
.attr("cy", function(d, i) {
return y(d.pfcLevel)
})
.attr("r", 2);

D3: How to validate data during data binding?

I am trying to render a gantt chart, where I am binding my data in d3 and rendering circle in both end. My data is somewhat similar to this structure:
function Event(start, end) {
this.startTime = start;
this.endTime = end;
}
I bind my data as usual:
myplot.selectAll(".EventStart")
.data(EventList).enter()
.append("circle")
.attr("class", "EventStart")
.attr("cx", function (d) { return scaleX(d.startTime)})
.attr("cy", function (d) { return eventRenderingHeight })
.attr("r", 5)
.style("fill", "white");
myplot.selectAll(".EventEnd")
.data(EventList).enter()
.append("circle")
.attr("class", "EventEnd")
.attr("cx", function (d) { return scaleX(d.endTime)})
.attr("cy", function (d) { return eventRenderingHeight })
.attr("r", 5)
.style("fill", "white");
Now, this will render two white circle at teh begining and end of my events.
But I want to omit rendering the 2nd circle if startTime and EndTime is same.
How can I do it?
Thanks.
You can either filter the dataList before binding
myplot.selectAll(".EventEnd")
.data(EventList.filter(function(d){ return d.startTime!=d.endTime }))
.enter()
.append("circle")
.attr("class", "EventEnd")
.attr("cx", function (d) { return scaleX(d.endTime)})
.attr("cy", function (d) { return eventRenderingHeight })
.attr("r", 5)
.style("fill", "white");
OR
Filter as shown below.
myplot.selectAll(".EventEnd")
.data(EventList)
.enter()
.append("circle")
.filter(function(d) { return d.startTime!=d.endTime })
.attr("class", "EventEnd")
.attr("cx", function (d) { return scaleX(d.endTime)})
.attr("cy", function (d) { return eventRenderingHeight })
.attr("r", 5)
.style("fill", "white");

Data missing when using group-element as opposed to dots

I use this code:
svg.selectAll(".dot")
.data(data)
.enter().append("circle")
.attr("class", "dot")
.attr("r", function(d) { return domainOnlyScale(parseFloat(d.Size)+0.01); } )
.attr("cx", function(d) {return x(d.x+(Math.random() * 0.25) - 0.125); })
.attr("cy", function(d) { return y(d.y+(Math.random() * 0.25) - 0.125); })
.style("fill", function(d) { return color(d.color); })
.style("stroke-width", "1px")
.style("stroke", function(d) { return strokecolor(d.color); });
And everything works. All data points show up. Now I change this part of the code to:
console.log(data); // Shows all data points!
var groupings = svg.selectAll("g")
.data(data)
.enter()
.append("g")
.attr("transform", function(d){return "translate("+(x(d.x+(Math.random() * 0.25) - 0.125))+","+y(d.y+(Math.random() * 0.25) - 0.125)+")"}); //used console.log here)
groupings.append("circle")
.attr("class", "dot")
.attr("r", function(d) { return domainOnlyScale(parseFloat(d.size)+0.01); } )
.style("fill", function(d) { return color(d.color); })
.style("stroke-width", "1px")
.style("stroke", function(d) { return strokecolor(d.color); });
groupings.append("text")
.attr("class", "bubbletext")
.attr("dx", function(d){ return -4 })
.attr("dy", function(d){ return +5 })
.text( function(d) { return d.category.substring(0,1); } );
I used another console.log inside the function of the transform of the groupings-creation, and the data is already filtered there. From a few tries it seems as if the first ~15 entries are missing.
Thanks in advance!
The problem is that you have g elements on the page already. These are getting selected through svg.selectAll("g") and then matched to data. Hence, the enter selection doesn't contain all the elements you expect to be there.
The fix is simple -- assign a class to those g elements you're using here to be able to distinguish them from the rest and select accordingly:
svg.selectAll("g.dot")
.data(data)
.enter()
.append("g")
.attr("class", "dot");

d3 Multiline Graph Update

I have a multiline graph that displays 10 series of data, I am trying to get the lines to update with new data but for some reason I can't get that happening.
The transition with the new data is working for the points on the lines so I assume I am not selecting the right elements but for the life of me I can't figure out where my mistake is.
At one point I had one line changing which indicated it was only updating from the first index of the data array.
Any insight would be appreciated:
Initial Series creation-
var series = svg.selectAll(".series")
.data(seriesData)
.enter().append("g")
.attr("class", "series");
series.append("path")
.attr("id", function (d) {
return d.name;
})
.attr("stay", "false")
.attr("class", "line")
.attr("d", function (d) {
d.line = this;
return line(d.values);
})
.attr("opacity", ".2")
.on("click", function () {
fadeOuts(this);
})
.style("stroke", function (d) {
return strokeCol;
})
.style("stroke-width", "4px")
.style("fill", "none");
Update function:
This is where I am stuck, the points respond to the new data but the paths do not.
series.data(newseriesData);
series.selectAll("path")
.attr("id", function (d) {
return d.name;
})
.attr("d", function (d) {
d.line = this;
return line(d.values);
})
.attr("opacity", ".2")
.on("click", function () {
fadeOuts(this);
})
.style("stroke", function (d) {
return strokeCol;
})
.style("stroke-width", "4px")
.style("fill", "none");
series.selectAll(".point")
.data(function (d) {
return d.values;
})
.transition()
.attr("cx", function (d) {
return x(d.label) + x.rangeBand() / 2;
})
.attr("cy", function (d) {
return y(d.value);
})
.style("fill", function (d) {
return color(d.name);
})
.style("stroke", "grey")
.style("stroke-width", "2px")
.on("mouseover", function (d) {
showPopover.call(this, d);
})
.on("mouseout", function (d) {
removePopovers();
})
Yes this is a university project, this is the last piece of work in a solid 50+ hour effort on this and I'd just like to get it knocked out.
The short answer is that instead of series.selectAll("path") you should use series.select("path"). Remember that series is already a selection, and the subselection is done for each element in it. You've appended exactly one element to each of the selection, so .select() is fine and no .selectAll() is required.
The main difference this makes is that .select() inherits the data from the parent selection, while .selectAll() doesn't -- when doing .selectAll() the data is simply not updated and therefore no change occurs.

Categories

Resources