Reload nested data in D3.js - d3.js

I do not manage to update a bar-chart with nested data in D3.js with new data.
I have nested data of the form:
data = [[1,2,3,4,5,6],[6,5,4,3,2,1]];
I managed to visualize the data by first appending a group for every subarray.
In the groups I then add the arrays as data (simplified):
function createGraph(l, svg){
var g = svg.selectAll("g")
.data(l)
.enter().append("g");
var rect = g.selectAll("rect)
.data(function(d){return d;})
.enter().append("rect")
. ...
}
However, when call the function again with different data, nothing happens.
It seems like in the second row, the rects do not get updated.
I have created a full example over at jsBin: http://jsbin.com/UfeCaGe/1/edit?js,output

A little more explanation of Lars' bug-catch, since I'd already started playing around...
The key was in this section of the code:
var group = svg.selectAll("g")
.data(l)
.enter().append("g");
The variable group is assigned the enter selection, not the raw selection. Then in the next line:
var bar = group.selectAll("rect")
.data(function(d){
return d;
});
You end up defining bar as only the rectangles that are children of just-entered groups. So even though you were handling update correctly for the rectangles, that whole section of code wasn't even running. You need to save the group selection before branching the chain to deal with entering groups:
var group = chart.selectAll("g")
.data(dt);
group.enter().append("g");
var bar = group.selectAll("rect")
.data(function(d){
return d;
});
Also, you're missing a j in your function declaration in your update. And you can reduce code duplication by putting your rectangle update code after your rectangle enter code, and then any attributes that get set in the update don't have to be specified for enter. (Some older examples don't use this pattern, because the original versions of d3 didn't automatically transfer newly-entered elements to the main selection.)
// enter
bar.enter().append("rect")
.attr("fill", function(d,i,j){
return colors(j);})
.attr("height", 0);
// update
bar.attr("transform", function(d, i, j) {
x = "translate("+(i*2.2*w+j*w)+",0)";
return x; })
.transition()
.duration(750)
.attr("width", w)
.attr("height", function(d){return d*10;});

Related

Heatmap and sparklines

I created an heatmap using this example and this data:
NAME,YEAR,M1,M2
A,2000,20,5
B,2000,30,1
C,2000,,10
D,2000,,88
E,2000,,21
F,2000,84,3
G,2000,,64
A,2001,44,48
B,2001,15,51
C,2001,20,5
D,2001,95,2
E,2001,82,9
F,2001,,77
G,2001,3,80
A,2002,8,99
B,2002,92,52
C,2002,62,
D,2002,41,
E,2002,66,
F,2002,21,21
G,2002,62,4
A,2003,2,5
B,2003,89,78
C,2003,9,
D,2003,7,9
E,2003,2,45
F,2003,92,58
G,2003,2,14
A,2004,2,55
B,2004,89,58
C,2004,9,55
D,2004,7,59
E,2004,2,70
F,2004,92,
G,2004,2,
Now I would like to add to the right of the heatmap a sparkline for each row, so there must be a sparkline associated with A, to B, etc.
And I wish they were positioned right next to each other.
To make the sparklines I saw this example.
This is the result: PLUNKER.
As you can see, I can't get the data correctly from the data.csv file to create the sparklines. Also I don't know how to place them in the correct position.
I tried this way but without success.
var sparkSvg = d3.select("#container-sparkline")
.append("svg")
.attr("width", "100%")
.attr("height", "100%")
.data(dataNest)
.enter()
.append("path")
.attr("class", "sparkline-path")
.attr("d", function(d) {
console.log("i");
console.log(d);
});
Also I'm not sure using two div is the correct way to put a chart near another chart.
Anyone would know how to help me?
Approach:
I've created a sparkline for every name in data set with values on x axis as a year and y as a m2 value from data set. For the demo purposes I've hardcoded number of years to 5 so x axis have only 5 values, but that can be computed with some additional script based on input data.
I've also added tome padding for sparkline container so they're aligned with the heatmap
Code:
As you can see in the plunker I've introduced a function to group data by name, so for each name we have an array with objects:
var groupBy = function(array, key) {
return array.reduce(function(a, v) {
(a[v[key]] = a[v[key]] || []).push(v);
return a;
}, {});
};
// data grouped by name
var groupedData = groupBy(data, 'name');
Since we assumed for demo purposes that X axis has fixed number of values we need to find max value for Y axis to properly scale charts. To do that I reduce array of values to get only m2 values and find a max number whthin that array:
var maxYvalue = Math.max(...data.map(function(d){return Number(d.m2)}));
Now we can create scales for the sparklines
var x = d3.scaleLinear().domain([0, 4]).range([0, 60]);
var y = d3.scaleLinear().domain([0, maxYvalue]).range([2, itemSize-2 ]);
I'm assuming that chart have width of 60px and height of itemSize, I also introduce 2px of vertical padding so its easier to read those sparklines being on next to each-other.
Now we can define d3.line(as you already did in your plunker) which we'll use fro rendering sparklines .
var line = d3.line()
.x(function(d, i) { return x(i); })
.y(function(d) { return y(d); })
And last step is to render sparklines inside "#container-sparkline" container. To do that we can iterate over every array in groupedData and render sparkline for each name:
// for each name render sparkline
Object.keys(groupedData).forEach(function(key){
const sparkData = groupedData[key].map(function(datum){
return Number(datum['m2']);
})
var sparkSvg = d3.select("#container-sparkline")
.append("svg")
.attr("width", "100%")
.attr("height", itemSize-1)
.append("path")
.attr("class", "sparkline-path")
.attr("d", line(sparkData));
})
I've also slightly changed styles for #container-sparkline and added borders for sparkline svg's. I hope this is what you've asked for.
Here you can find your plunker with my changes
http://plnkr.co/edit/9vUFI76Ghieq4yZID5B7?p=preview

D3.js binding nested data

I'm really new to coding, and also to asking questions about coding. So let me know if my explanation is overly complex, or if you need more context on anything, etc.
I am creating an interactive map of migration flows on the Mediterranean Sea. The flows show origin and destination regions of the migrant flows, as well as the total number of migrants, for Italy and Greece. Flows should be displayed in a Sankey diagram like manner. Because I am displaying the flows on a map and not in a diagram fashion, I am not using D3’s Sankey plugin, but creating my own paths.
My flow map, as of now (curved flows are on top of each other, should line up next to each other)
For generating my flows I have four points:
2 points for the straight middle part of the flow (country total)
1 point each for the curved outer parts (origin and destination region), using the two points of the straight middle part as starting points
The straight middle and both curved outer parts are each generated independently from their own data source. Flow lines are updated by changing the data source and calling the function again. The flow lines are generated using the SVG path mini-language. In order for the curved outer parts of the flows to show correctly, I need them to be lined up next to each other. To line them up correctly, I need to shift their starting points. The distance of the shift for each path element is determined by the width of the path elements before it. So, grouping by country, each path element i needs to know the sum of the width of the elements 0-i in the same group.
After grouping my data with d3.nest(), which would allow me to iterate over each group, I am not able to bind the data correctly to the path elements
I also can't figure out a loop function that adds up values for all elements 0-i. Any help here? (Sorry if this is kind of unrelated to the issue of binding nested data)
Here is a working function for the curved paths, working for unnested data:
function lineFlow(data, flowSubGroup, flowDir) {
var flowSelect = svg.select(".flowGroup").select(flowSubGroup).selectAll("path");
var flow = flowSelect.data(data);
var flowDirection = flowDir;
flow.enter()
.append("path").append("title");
flow
.attr("stroke", "purple")
.attr("stroke-linecap", "butt")
.attr("fill", "none")
.attr("opacity", 0.75)
.transition()
.duration(transitionDur)
.ease(d3.easeCubic)
.attr("d", function(d) {
var
slope = (d.cy2-d.cy1)/(d.cx2-d.cx1),
dist = (Math.sqrt(Math.pow((d.rx2-d.rx1),2)+Math.pow((d.ry2-d.ry1),2)))*0.5,
ctrlx = d.rx1 + Math.sqrt((Math.pow(dist,2))/(1+Math.pow(slope,2)))*flowDirection,
ctrly = slope*(ctrlx-d.rx1)+d.ry1;
return "M"+d.rx1+","+d.ry1+"Q"+ctrlx+","+ctrly+","+d.rx2+","+d.ry2})
.attr("stroke-width", function(d) {return (d.totalmig)/flowScale});
flowSelect
.select("title")
.text(function(d) {
return d.region + "\n"
+ "Number of migrants: " + addSpaces(d.totalmig)});
};
I tried adapting the code to work with data grouped by country:
function lineFlowNested(data, flowSubGroup, flowDir) {
var g=svg.select(".flowGroup").select(flowSubGroup).append("g").data(data).enter();
var gflowSelect=g.selectAll("path");
var gflow=gflowSelect.data (function(d) {return d.values});
gflow.enter()
.append("path");
gflow.attr("stroke", "purple")
.attr("stroke-linecap", "butt")
.attr("fill", "none")
.attr("opacity", 0.75)
// .transition()
// .duration(transitionDur)
// .ease(d3.easeCubic)
.attr("d", function(d) {
var
slope = (d.cy2-d.cy1)/(d.cx2-d.cx1),
dist = (Math.sqrt(Math.pow((d.rx2-d.rx1),2)+Math.pow((d.ry2-d.ry1),2)))*0.5,
ctrlx = d.rx1 - Math.sqrt((Math.pow(dist,2))/(1+Math.pow(slope,2)))*flowDirection,
ctrly = slope*(ctrlx-d.rx1)+d.ry1;
return "M"+d.rx1+","+d.ry1+"Q"+ctrlx+","+ctrly+","+d.rx2+","+d.ry2})
.attr("stroke-width", function(d) {return (d.totalmig)/flowScale});
};
which isn't working. What am I doing wrong? Thanks for any hints!

D3 creating a selector from this inside each

I would like to be able to place each data object (in this case 'moreData' array) inside a group element. So in the very simplified example below I would end up with three groups with 2 or 3 circles inside.
I'm using the node D3 provides with 'this' in a call to each (second one) to construct a selector.
Although the first call to each is correct (console.log tells me so)... the selector I create is obviously not doing the right thing as its creating 5 circles outside the body element and the second console.log never reports the first element.
Here is a fiddle simple use of this
From this simple data set of three objects:
data = [{'data':10, 'moreData':[1,2]}, {'data': 12, 'moreData':[3,4,5]},{'data':6, 'moreData':[7,8,9]}];
I expect and get three groups but no circles inside the groups.
var svg = d3.select("body").append("svg");
var shapes = svg.selectAll("g")
.data(data).enter();
shapes.append("g").each(add);
function add(d, i) {
console.log(i, d);
// this is where we go south!!
d3.select(this).data(d.moreData).enter() // help with this!!
.append("circle")
.attr("cx", function (d, i) {
return (i + 1) * 25;
})
.attr("cy", 10)
.attr("r", 10)
.each(function (d, i) {
console.log(i, d); // this is not good!
})
thanks for any insight into what I'm doing wrong....
The above fiddle shows no output, but if you inspect the 'results' tab you can see the correct empty groups and the circle elements outside the body tag ... at least in Chrome.
You need to select the empty set of circles before setting the data.
Right now, you are calling:
d3.select(this).data(d.moreData)
Replace that line with:
d3.select(this).selectAll("circle").data(d.moreData)
The general d3 enter paradigm is select a group -> attach data to that group -> use enter/exit, where enter will run for each item in the group which has data, but no DOM element, and exit for each element which has a DOM element but no data.
Fiddle.
Also, you should use different variables for i and d for your inner function, since right now they're the same as the variables on your outer function. (Perhaps use function(D, I) instead.)

joining multiple parts of a g element to the same data in d3

I am trying to figure out how to transition paths and text bound to the same data within a g element.
This gist shows the behavior I want. When you click the change button the path and text positions transition smoothly. The problem is I accomplish this by separately joining the path and text elements to the data. I am not the only one using join this way, but I think it violates the goal of maintaining one to one mapping between elements and data with d3.
Is there a way to emulate the
var path = svg.selectAll("path")
.data(dataset);
path
.enter().append("path")
.attr("d", function(d) { return line(d.values) + "Z"; })
.style("fill", function(d, i) { return color(i); });
path
.transition().duration(500)
.attr("d", function(d) { return line(d.values) + "Z"; });
path
.exit().remove();
pattern for elements linked to the same data within a g element?
Here is one of my failed attempts. Rather than updating the paths and text, the change button causes new g elements to be added.
Added on edit: failed attempt now works after correcting the typo found by explunit
You have a typo in your class name on the join. This code:
var g = svg.selectAll(".shapes")
.data(dataset);
Should be this:
var g = svg.selectAll(".shape")
.data(dataset);
When I change it as above it works fine for me.

Accessing elements associated with data using D3 after they have been generated

I have a dataset, each item has been linked to svg rects using D3.
var bars = svg_content.selectAll("rect")
.data(dataset);
.enter()
.append("rect")
Assume the generation is complete (i.e. the .enter() process is complete and the rects have been generated).
How would I access the rect associated with a specific index of that dataset (for instance, the rect linked to the third piece of data)?
You can use selection.filter or the function form of the commonly used selection.select depending on your needs:
var third = selection.filter(function(d, i) { return i == 2; });
// Equivalently
var third = selection.select(function(d, i) { return i == 2; });
There are a few ways to do this. Generally, in d3, you tend to access the data from within a selection. So you would see something like:
var bars = svg_content.selectAll("rect")
.data(dataset);
.enter()
.append("rect")
.attr('class', function(d) { return d.myName; });
Here d is the data item from dataset that is associated with a particular rect. That code would class each rect with the "myName" property of each data item.
Let's say some you want to place one of these rects specially. One with myName='aName'. We will select that rectangle and set the 'tranform' attribute based on the associated data.
svg.content.selectAll('rect.aName')
.attr('transform', function(d) { return 'translate(' + d.x + ',' + d.y + 20 ')'; })
Note that in both cases you can also access the item's index and if it's relevant also the parent index (use function(d,i,j) {...})
Finally, though I don't encourage it in general, I have for unit tests directly accessed the data associated with an element with __data__. For example with jQuery:
$.find("svg rect.aName")[0].__data__;
You can play with a quick fiddle here

Resources