Children elements didn't inherit parent data with D3 force layout [closed] - d3.js

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 6 years ago.
Improve this question
I have created a d3 force layout,and works very well.
My mainly code like so:
var nodes = [{id:1, n:'n_1',np:'0'},{id:2, n:'n_2',np:'0'}];//just for demo
//1. set data
var update = svg.selectAll(".node").data(nodes);
//2. enter
update.enter().append("svg:g").attr("class", "node")
.call(function(p){
p.append("svg:image").attr("class", "nodeimage");
p.append("svg:text").attr("class", "nodetext");
});
//3. exit
update.exit().remove();
As is known to us, d3.selectAll(".node").data() is my data. Because the child elements of g will inherit the data from the parent data, d3.selectAll(".nodeimage").data() is also my data.Am I right?
In fact, my data nodes is from backend, and the data is updated. For example, some properties like np have been changed from 0 to 1. We consider the result is
nodes = [{id:1, n:'n_1',np:'1'},{id:2, n:'n_2',np:'0'}];
I need to call the function above again. However,d3.selectAll(".node").data() is right, while d3.selectAll(".nodeimage").data() is wrong now.
The following code will not work well.
d3.selectAll('.nodeimage').attr("test", function(d){
//d.np is a wrong value.
});
Any suggestions for me?
Here is my demo of jsfiddle:http://jsfiddle.net/bpKG4/663/

This is a strange behavior of d3. If I understand correctly (which is not granted), selection.data(...) automatically transfers data to child elements, unless they already have some data binded.
In your case, it means that you need to copy "by hand" the data to each child:
//select any child node, then:
.each(function() {
d3.select(this).datum(d3.select(this.parentNode).datum());
})
NB: in your fiddle, you only set the xlink:href in the enter() selection: this is wrong, you need to set it within the whole update selection.
update.selectAll(".nodeimage")
.each(function() {
d3.select(this).datum(d3.select(this.parentNode).datum());
})
.attr("xlink:href", function(d){
var img;
if(d.np == 1){
img = "http://www.gravatar.com/avatar/1eccef322f0beef11e0e47ed7963189b/?default=&s=80"
}else{
img = "http://www.gravatar.com/avatar/a1338368fe0b4f3d301398a79c171987/?default=&s=80";
}
return img;
});
See here: http://jsfiddle.net/cs4xhs7s/1/

Related

Unable to filter individual stacks using dc.js with multiple X keys

Stacked Bar chart not able to filter on click of any Stack
I need to filter all the charts when clicking on any stack, which is not happening and struggling for a few days.
I've created a fiddle with link
http://jsfiddle.net/praveenNbd/09t5fd7v/13/
I feel am messing up with keys creation as suggested by gordonwoodhull.
function stack_second(group) {
return {
all: function () {
var all = group.all(),
m = {};
// build matrix from multikey/value pairs
all.forEach(function (kv) {
var ks = kv.key;
m[ks] = kv.value;
});
// then produce multivalue key/value pairs
return Object.keys(m).map(function (k) {
return {
key: k,
value: m[k]
};
});
}
};
}
I tried to follow this example https://dc-js.github.io/dc.js/examples/filter-stacks.html
Not able to figure out how below code works:
barChart.on('pretransition', function (chart) {
chart.selectAll('rect.bar')
.classed('stack-deselected', function (d) {
// display stack faded if the chart has filters AND
// the current stack is not one of them
var key = multikey(d.x, d.layer);
//var key = [d.x, d.layer];
return chart.filter() && chart.filters().indexOf(key) === -1;
})
.on('click', function (d) {
chart.filter(multikey(d.x, d.layer));
dc.redrawAll();
});
});
Can someone please point me out in the right direction.
Thanks for stopping by.
You usually don't want to use multiple keys for the X axis unless you have a really, really good reason. It is just going to make things difficult
Here, the filter-stacks example is already using multiple keys, and your data also has multiple keys. If you want to use your data with this example, I would suggest crunching together the two keys, since it looks like you are really using the two together as an ordinal key. We'll see one way to do that below.
You were also trying to combine two different techniques for stacking the bars, stack_second() and your own custom reducer. I don't think your custom reducer will be compatible with filtering by stacks, so I will drop it in this answer.
You'll have to use the multikey() function, and crunch together your two X keys:
dim = ndx.dimension(function (d) {
return multikey(d[0] + ',' + d[1], d[2]);
});
Messy, as this will create keys that look like 0,0xRejected... not so human-readable, but the filter-stacks hack relies on being able to split the key into two parts and this will let it do that.
I didn't see any good reason to use a custom reduction for the row chart, so I just used reduceCount:
var barGrp = barDim.group();
I found a couple of new problems when working on this.
First, your data doesn't have every stack for every X value. So I added a parameter to stack_second() include all the "needed" stacks:
function stack_second(group, needed) {
return {
all: function() {
var all = group.all(),
m = {};
// build matrix from multikey/value pairs
all.forEach(function(kv) {
var ks = splitkey(kv.key);
m[ks[0]] = m[ks[0]] || Object.fromEntries(needed.map(n => [n,0]));
m[ks[0]][ks[1]] = kv.value;
});
// then produce multivalue key/value pairs
return Object.entries(m).map(([key,value]) => ({key,value}));
}
};
}
Probably the example should incorporate this change, although the data it uses doesn't need it.
Second, I found that the ordinal X scale was interfering, because there is no way to disable the selection greying behavior for bar charts with ordinal scales. (Maybe .brushOn(false) is completely ignored? I'm not sure.)
I fixed it in the pretransition handler by explicitly removing the built-in deselected class, so that our custom click handler and stack-deselected class can do their work:
chart.selectAll('rect.bar')
.classed('deselected', false)
All in all, I think this is way too complicated and I would advise not to use multiple keys for the X axis. But, as always, there is a way to make it work.
Here is a working fork of your fiddle.

Adjusting small multiples sparklines

I have an heatmap that show some data and a sparkline for each line of the heatmap.
If the user click on a row label, then the data are ordered in decreasing order, so each rect is placed in the right position.
Viceversa, if the user click on a column label.
Each react is placed in the right way but I'm not able to place the sparkline.
Here the code.
When the user click on a row label, also the path inside the svg containing the sparkline should be updated.
And then, when the user click on a column label, the svg containing the sparkline should be placed in the correct line.
To place the svg in the right place, I try to use the x and y attributes of svg. They are updated but the svg doesn't change its position. Why?
Here is a piece of code related to that:
var t = svg.transition().duration(1000);
var values = [];
var sorted;
sorted = d3.range(numRegions).sort(function(a, b) {
if(sortOrder) {
return values[b] - values[a];
}
else {
return values[a] - values[b];
}
});
t.selectAll('.rowLabel')
.attr('y', function(d, k) {
return sorted.indexOf(k) * cellSize;
});
Also, I don't know how to change the path of every sparkline svg. I could take the data and order them manually, but this is only good for the row on which the user has clicked and not for all the others.
How can I do?
The vertical and horizontal re-positioning/redrawing of those sparklines require different approaches:
Vertical adjustment
For this solution I'm using selection.sort, which:
Returns a new selection that contains a copy of each group in this selection sorted according to the compare function. After sorting, re-inserts elements to match the resulting order.
So, first, we set our selection:
var sortedSVG = d3.selectAll(".data-svg")
Then, since selection.sort deals with data, we bind the datum, which is the index of the SVG regarding your sorted array:
.datum(function(d){
return sorted.indexOf(+this.dataset.r)
})
Finally, we compare them in ascending order:
.sort(function(a,b){
return d3.ascending(a,b)
});
Have in mind that the change is immediate, not a slow and nice transition. This is because the elements are re-positioned in the DOM, and the new structure is painted immediately. For having a slow transition, you'll have to deal with HTML and CSS inside the container div (which may be worth a new specific question).
Horizontal adjustment
The issue here is getting all the relevant data from the selection:
var sel = d3.selectAll('rect[data-r=\'' + k + '\']')
.each(function() {
arr.push({value:+d3.select(this).attr('data-value'),
pos: +d3.select(this).attr('data-c')});
});
And sorting it according to data-c. After that, we map the result to a simple array:
var result = arr.sort(function(a,b){
return sorted.indexOf(a.pos) - sorted.indexOf(b.pos)
}).map(function(d){
return d.value
});
Conclusion
Here is the updated Plunker: http://next.plnkr.co/edit/85fIXWxmX0l42cHx or http://plnkr.co/edit/85fIXWxmX0l42cHx
PS: You'll need to re-position the circles as well.

D3.js Stacked Bar Chart Selects

I am trying to come to grips with some D3 concepts but feel as though there are some fundamentals gaps in my knowledge. It seems to do with how the D3 stack() function works.
I am trying to understand why the following two code snippets are not equivalent. The first works and populates data, the second does not.
First (Working Code, Simplified):
var mainStack = d3.stack().keys(keys);
var seriesData = mainStack(dataset[0]);
gBars = g.selectAll("g")
.data(seriesData, function (d, i) { return (i); })
.enter().append("g").. more work here
Second (Not Working, Simplifed):
var mainStack = d3.stack().keys(keys);
var seriesData = mainStack(dataset[0]);
gBars = g.selectAll("g")
.data(seriesData, function (d, i) { return (i); });
gBars.enter().append("g").. more work here
Basically, I have just tried to break up the code to make it simpler (for me) to read, and also to allow me to implement an exit() function. However, when i do the above, the graphs fail to display.
I thought that the gBar variables should maintain their previous selects?
Any assistance would be appreciated, I have successfully used this pattern for simple charts, hence my suspicion that this is related to something I am missing when the d3.stacked() function is involved which nests the data?
With some friendly help, I found that the difference is in the way v4 handles selects. The answer was to utilise merges to combine the various selects and then perform any combined updates on the merged nodes.
Eg:
var mainStack = d3.stack().keys(keys);
var seriesData = mainStack(dataset[0]);
var gBarsUpdate = g.selectAll("g")
.data(seriesData, function (d, i) { return (i); });
var gBarsEnter = gBarsUpdate.enter().append("g")
var gBars = gBarsEnter.merge(gBarsUpdate)
//now update combined with common attributes as required
gBars.attr("fill", "#ff0000"); //etc
Hope this helps anyone else a bit confused by this. Took me a bit of time to understand what was going on, but thanks to some smart people, they put me on the right track :-)
ps. My problem ended up having nothing to do with the stack() function.

Extracting a selection of countries from JSON file

I'm using this great example: Countries By Area
However I'm wanting to modify this code for my own use and project only a selection of chosen countries.
I've managed to read the JSON file into an array, and the code is now looking through that array but I don't see any way on rendering a country (or countries) if a criteria is met.
For example, if I want to simply render the country, who has an ID of 533, I don't see any way of attaching a condition.
Can anyone shed any light on how I may be able to do this.
Have edited my original question here as I've managed to do it, but I'm sure there's a more elegant way of achieving it:
Original code was:
var svg = d3.select("#map").selectAll("svg")
.data(topojson.feature(world, world.objects.countries).features)
Which I've changed to:
var svg = chartDetails.plotArea.selectAll("svg")
.data(
function(d){
a = topojson.feature(tempWorld, tempWorld.objects.countries).features
var returnobject =[]
$.each(a, function (i, v) { if (v.id == 826) { returnobject.push(v) } });
return returnobject
})
826 refers to United Kingdom.
If your array looks like this:
var a = [];
a.push({id:1, name:'Argentina'});
a.push({id:2, name:'Bahamas'});
Then you might need to iterate it to find the key(id) you want.
Take a look at these solutions:

select only updating elements with d3.js

With a d3.js join Is there a way to select only the 'updating' elements separately from the 'entering' elements?
updateAndEnter = d3.selectAll('element').data(data);
entering = updateAndEnter.enter();
exiting = updateAndEnter.exit();
updatingOnly = ??;
Yes, the selection just after the data join contains the 'update only' elements. After appending to the enter() selection, it will be expanded to include the entering elements as well.
See General Update Pattern:
// DATA JOIN
// Join new data with old elements, if any.
var text = svg.selectAll("text")
.data(data);
// UPDATE
// Update old elements as needed.
text.attr("class", "update");
// ENTER
// Create new elements as needed.
text.enter().append("text")
.attr("class", "enter")
.attr("x", function(d, i) { return i * 32; })
.attr("dy", ".35em");
// ENTER + UPDATE
// Appending to the enter selection expands the update selection to include
// entering elements; so, operations on the update selection after appending to
// the enter selection will apply to both entering and updating nodes.
text.text(function(d) { return d; });
// EXIT
// Remove old elements as needed.
text.exit().remove();
it's my pleasure
For me ( too ) this is a little bit confusing : it seems that the only available set is actually ENTER+UPDATE ( blended together ) and EXIT.
But what if i want to work or at least identify only updated elements?
I wrote a very simple function ( that follows, simply put wrap it in a script tag at the end of a basic html page ) showing this simple dilemma : how do I highlight updated elements ? Only ENTER and EXIT seem to react "correctly"
To test it, just type in chrome console :
manage_p(['append','a few','paragraph'])
manage_p(['append','a few','new','paragraph'])
manage_p(['append','paragraphs'])
I can get green or red highlighting, i can't get white
Maybe we're missing D3Js specs?
Best regards,
Fabrizio
function join_p(dataSet) {
var el = d3.select('body');
var join = el
.selectAll('p')
.data(dataSet);
join.enter().append('p').style('background-color','white');
join.style('background-color','green').text(function(d) { return d; });
join.exit().text('eliminato').style('background-color','red');
}

Resources