D3 access nested data - javascript

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);

Related

How to coordinate interactions between multiple data visualizations, particularly when one of them uses nesting? JavaScript and D3

For a project I am attempting to have three visualizations for data based on car stats, where if you hover over one, the others will show the affects of that hovering as well.
The first is a bar graph, the second is a scatterplot, and the third is a line graph. For the line graph I wanted to group by manufacturer so that I don't have a couple hundred lines on my line graph, as the plot coordinates on the x and y are acceleration and model year. The other two don't need to be grouped in this way because one of their axes is the manufacturer.
I have the interactions from the line graph to the other two working since there is no nesting on the bar or scatterplot, and both the scatterplot and the bar graph can affect each other perfectly fine, but since the data is nested for the line graph, I can't seem to figure out how to access it, as the way I was doing it for the other two (using filtering) does not seem to work.
Below I am first showing where I am trying to create interactions when the mouse hovers (this is for the bar graph), and below that I include how my line graph is set up to show how it works. All I want is to make the corresponding line stand out more from the others by thickening the stroke when I hover over the bar or plot (in the scatterplot), and then go back to the normal size upon moving my cursor.
I followed the tutorial on the D3 website for line graphs, so there shouldn't be anything particularly wrong with that code.
Creating the bars for the bar graph, the mouseover and mouseout are the important parts:
var path1 = svg1.selectAll("myRect")
.data(data)
.enter()
.append("rect")
.attr("x", x1(0.1) )
.attr("y", function(d) { return y1(d.Manufacturer); })
.attr("height", y1.bandwidth() )
.attr("width", function(d) { return x1(d.Cylinders); })
.attr("fill", function (d) {
return color1(d.Cylinders);
})
.on('mouseover', function (d, i) {
svg1.selectAll('rect')
.filter(function(f) {
return f.Manufacturer === d.Manufacturer;
})
.attr("fill", function (d) {
return color4(d.Cylinders);
})
svg2.selectAll('circle')
.filter(function(f) {
return f.Manufacturer === d.Manufacturer;
})
.attr('r', 9)
.attr("fill", function (d) {
return color5(d.Horsepower);
});
svg3.selectAll('path') //THIS IS THE LINE GRAPH
.filter(function(f) {
console.log(this)
return ; // <-------This is where I don't know what to return to just get one line
})
.attr("stroke-width", 7)
})
.on('mouseout', function (d, i) {
svg1.selectAll('rect')
.filter(function(f) {
return f.Manufacturer === d.Manufacturer;
})
.attr("fill", function (d) {
return color1(d.Cylinders);
});
svg2.selectAll('circle')
.filter(function(f) {
return f.Manufacturer === d.Manufacturer;
})
.attr('r', 5)
.attr("fill", function (d) {
return color2(d.Acceleration);
});
d3.selectAll('path') //DELESLECTING LINE GRAPH
.filter(function(f) {
return f.key === d.Manufacturer; //this is what I tried before but it doesn't work
})
.attr("stroke-width", 1.5)
});
Creating the line graph:
var sumstat = d3.nest()
.key(function(d) { return d.Manufacturer;})
.entries(data);
// Add X axis
var x3 = d3.scaleLinear()
.domain([69, 84])
.range([ 0, width3 ]);
svg3.append("g")
.attr("transform", "translate(0," + height3 + ")")
.call(d3.axisBottom(x3).ticks(5));
// Add Y axis
var y3 = d3.scaleLinear()
.domain([8, d3.max(data, function(d) { return +d.Acceleration; })])
.range([ height3, 0 ]);
svg3.append("g")
.call(d3.axisLeft(y3));
var div3 = d3.select("#my_div").append("div")
.attr("class", "#tool_tip")
.style("opacity", 0)
.style("font-size", "xx-large");
// color palette
var res = sumstat.map(function(d){ return d.key }) // list of group names
var color = d3.scaleOrdinal()
.domain(res)
.range(['darkolivegreen','darkred','palevioletred','indianred', 'hotpink'])
// Draw the line
svg3.selectAll(".line")
.data(sumstat)
.enter()
.append("path")
.attr("fill", "none")
.attr("stroke", function(d){ return color(d.key) })
.attr("stroke-width", 1.5)
.attr("d", function(d){
return d3.line()
.x(function(d) { return x3(d.ModelYear); })
.y(function(d) { return y3(+d.Acceleration); })
(d.values)
})
.on('mouseover', function (d, i) {
//highlight;
svg3.selectAll("path")
.attr("stroke-width", 0.9)
d3.select(this)
.attr("stroke", function(d){ return color(d.key)})
.attr("stroke-width", 6)
svg1.selectAll('rect')
.filter(function(f) {
return f.Manufacturer === d.key;
})
.attr("fill", function (d) {
return color4(d.Cylinders);
})
svg2.selectAll('circle')
.filter(function(f) {
return f.Manufacturer === d.key;
})
.attr('r', 9)
.attr("fill", function (d) {
return color5(d.Horsepower);
});
})
.on('mouseout', function (d, i) {
svg3.selectAll("path")
.attr("stroke-width", 1.5)
d3.select(this)
.attr("stroke", function(d){ return color(d.key)})
.attr("stroke-width", 1.5)
svg1.selectAll('rect')
.filter(function(f) {
return f.Manufacturer === d.key;
})
.attr("fill", function (d) {
return color1(d.Cylinders);
})
svg2.selectAll('circle')
.filter(function(f) {
return f.Manufacturer === d.key;
})
.attr('r', 5)
.attr("fill", function (d) {
return color2(d.Horsepower);
});
});
Any assistance I can get would be greatly appreciated!!
I think I may have figured out the problem. It would seem that trying to filter the paths causes an issue because the x and y axes are also technically lines, and thus have paths that are null. I tried
svg3.selectAll('path')
.filter(function(f) {
console.log(f)
if(f!=null)
return f.key === d.Manufacturer;
})
.attr("stroke-width",7)
In the .on('mouseover') function, and it seems to be working. The issue was the nulls, not the actual accessing of the keys.
Still taking suggestions if there is a better way to do this!

Add points to D3 chart

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.

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");

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.

D3: multidimensional array of lines

i want to add a multidimensional array of lines to my SVG with the D3 library. But somehow the lines don´t show up. There is no error message from javascript so i guess i can not be totally wrong but something is missing. I tried to use the description of Mike Bostock as an example http://bost.ocks.org/mike/nest/
I have an array of lines that looks like that:
var datasetPolylines = [[[-10849.0, 1142.0, -10720.0, 454.0],[x1, y1, x2, y2],[x1, y1, x2, y2]...],[polyline],[polyline]...];
For every Line there are 4 points in the array for x and y values of the line.
Now i try to add them to my mainsvg like that:
d3.select("#mainsvg").selectAll("g")
.data(datasetPolylines)
.enter()
.append("g")
.selectAll("line")
.data(function (d) {return d;})
.enter()
.append("line")
.attr("x1", function(d, i) {
return xScale(i[0]);
})
.attr("y1", function(d, i) {
return yScale(i[1]);
})
.attr("x2", function(d, i) {
return xScale(i[2]);
})
.attr("y2", function(d, i) {
return yScale(i[3]);
})
.attr("stroke-width", 2)
.attr("stroke", "blue")
.attr("fill", "none");
I´m very thankful for every hint on where I´m wrong. I´m on this stuff now for some days and just don´t get it :(
Everything works fine if i just draw one polyline with many lines and use the .data attribute just once. I cannot merge the lines to one path also, because they are not always connected and must be drawn seperately.
The complete (and now thanks to Christopher and Lars also working) code example looks like that:
var datasetLines = [];
var datasetPolylines = [];
/**Add a line to the dataset for lines*/
function addLineToDataset(x1, y1, x2, y2){
var newNumber1 = x1;
var newNumber2 = y1;
var newNumber3 = x2;
var newNumber4 = y2;
datasetLines.push([newNumber1, newNumber2, newNumber3, newNumber4]);
}
/**Add polyline to the dataset for polylines*/
function addPolyline(){
var polyline = [];
for (i in datasetLines) {
polyline[i] = datasetLines[i];
}
datasetPolylines.push(polyline);
}
/**Draw all polylines from the polylinearray to the svg*/
function showPolylineArray(){
//Create scale functions for x and y axis
var xScale = d3.scale.linear()
.domain([d3.min(outputRange, function(d) { return d[0]; }), d3.max(outputRange, function(d) { return d[0]; })])//get minimum and maximum of the first entry of the pointarray
.range([padding, w - padding]); //w, the SVGs width. without padding 0,w
var yScale = d3.scale.linear()
.domain([d3.min(outputRange, function(d) { return d[1]; }), d3.max(outputRange, function(d) { return d[1]; })])
.range([h - padding, padding]); //without padding h,0
d3.select("#mainsvg").selectAll("g")
.data(datasetPolylines)
.enter()
.append("g")
.selectAll("line")
.data(function (d) {return d;})
.enter()
.append("line")
.attr("x1", function(d) {
return xScale(d[0]);
})
.attr("y1", function(d) {
return yScale(d[1]);
})
.attr("x2", function(d) {
return xScale(d[2]);
})
.attr("y2", function(d) {
return yScale(d[3]);
})
.attr("stroke-width", 2)
.attr("stroke", "blue")
.attr("fill", "none")
.on('mouseover', function(d){ d3.select(this).style({stroke: 'red'}); })
.on('mouseout', function(d){ d3.select(this).style({stroke: 'blue'}); })
.append("title")
.text("Polyline");
}
Your code is almost working, except for the referencing of the data (as pointed out by Christopher). You didn't show us the complete code, but the bit you have should work fine with these modifications.
Complete example here.

Categories

Resources