Return D3 key of Selected - javascript

I know I can use d3.keys() to return all keys inside of an object, but I want to return the selected items key I'm targeting inside of a mouseover event.
I'm targeting elements in D3 like so:
var test = something.selectAll('rect')
.data(myData['groupSelection'])
.enter()
.append('rect')
.on('mouseover', function (d) {
console.log(d3.keys(d));
}
This will return that given selections keys though, when I really need a count of that items keys, for instance, if I select the second rect created from the data, it'd be nice for it to return 2.

All callbacks in D3 that get the data as an argument also get the index of the data as an argument. That is, instead of
.attr("foo", function(d) { ... });
you can also write
.attr("foo", function(d, i) { ... });
where d is the data and i the index of d in the array of data that you've passed to .data(). The same goes for .style(), .on(), etc.
For example, assume you have data [2,3] and elements with data 1 and 2 bound to them. Now if you do (note the key function to .data() to match elements by their contents)
var sel = d3.selectAll("element").data([2,3], function(d) { return d; });
you'll get non-empty enter (containing 3), update (containing 2) and exit (containing the element that 1 was bound to) selections. You can operate on each of these selections, e.g.
sel.attr("foo", function(d, i) { ... });
The i refers to the index within the selection. Each selection contains only one element, so you'll get 0 for i -- for each selection. That is, the code
sel.attr("foo", function(d, i) { console.log(i); });
sel.enter().attr("foo", function(d, i) { console.log(i); });
sel.exit().attr("foo", function(d, i) { console.log(i); });
will log 0 to the console three times. If your update selection was of length 3 for example (that is, three elements in the argument to .data() are matched up with DOM elements in the selection), you would get 0, 1, 2 on the console.

Related

How to guarantee old data is in d3's exit selection

Problem:
I'm trying to understand the behavior of d3's exit selection from the general update pattern.
Note: I'm using d3V5
Fiddle
Say I want to visualize the number "1".
var data = [{id:"1"}];
var text = svg.selectAll('.text').data(data);
text.enter()
.each((d) => console.log("first append " + d))
.append('text')
.text(d => d.id)
All well and good. But now say I'm tired of "1" and more interested in visualizing "2".
data = [{id:"2"}];
text = svg.selectAll('.text').data(data);
text.exit().each((d) => console.log("remove " + d)).remove();
The console does not log {id:"1"}. This item was not placed in the exit selection.
text.enter()
.each((d) => console.log("now append " + d))
.append('text')
.text(d => d.id)
Now I have a "1" and a "2" stacked right on top of one another.
Assumptions:
I had thought that when I do .data(data) d3 would do a diff between the dom and the data, and place any old dom nodes without corresponding entries in data in the exit selection. I had thought the 'id' field on the data would distinguish these data elements. That doesn't seem to be the case.
Question:
How do I get {id:"1"} in the exit selection?
Or how do I remove the dom node associated with {id:"1"}?
The confusion here began with an erroneous assumption. In most of the d3 examples I've seen, the data has the following format:
[ {'id': 1, 'info': 'something'}, {'id': 2, 'info': 'something else'}, ...]
I had been assuming that selection.data() performed a diff using the data's 'id' field by default.
It turns out that this isn't the case, and you need to offer your own key function.
From D3's selection docs:
If a key function is not specified, then the first datum in data is assigned to the first selected element, the second datum to the second selected element, and so on.
So I added a key function:
function idFunc(d) { return d ? d.id : this.id; }
var data = [{id:"1"}];
var text = svg.selectAll('text').data(data, idFunc);
text.enter()
.append('text')
.text(d => d.id)
Then, dom nodes no longer corresponding to items in the data array found their way into the exit selection, and I was able to remove them.
Conclusions:
(Always) Define a key function over your data when you .data(data, keyFunc)

d3.js iterate order changes

As project to get to know d3.js, I’m displaying tweets on a map in real-time. Everything has worked this far, and I’m very, very pleased with the library.
On the page, I’m trying to list all languages. On hover, I want all tweets of that language to pop up. All of this is done, except some items in the list pops up the tweets of another language. A wrong one, I might add.
This is how I project the dots on the map:
points.selectAll('circle')
.data(tweets)
.enter()
.append('circle')
// Attach node to tweet, so I can use refer to the nodes later
.each(function(d) {
d.node = this;
})
.attr('r', 1);
This is how I create the list:
var data = d3.nest()
// Group by language code
.key(function(d) { return d.lang.code; })
.entries(tweets)
// Sort list by amount of tweets in that language
.sort(function(a, b) {
return b.values.length - a.values.length;
});
var items = languages_dom
// Add items
.selectAll('li')
.data(data)
.enter()
.append('li');
// Used for debugging
.attr('data-lang', function(d) {
return d.key; // Group key = language code
})
// Set text
.text(function(d) {
var dt = d.values[0];
return dt.lang.name;
})
// Mouseover handler
.on('mouseover', function(d) {
// Compare attribute with
// These values are actually different
var attr = d3.select(this).attr('data-lang');
console.log(attr, d.key);
// Pop up each node
d.values.forEach(function(d) {
d = d3.select(d.node);
d.transition()
.duration(200)
.attr('opacity', 0.5)
.attr('r', 8);
});
});
Note that the script above is run several times. d.key refers to another value later in the chain, while I’m not modifying data in that chain.
Edit 22:08
Things seems to work fine when I’m not sorting the data. At least it’s a lead.
As noted in the comments, you're overwriting d in your forEach function. Instead, you should try something like
d.values.forEach(function(p) {
d3.select(p.node)
.transition()
.duration(200)
.attr('opacity', 0.5)
.attr('r', 8);
});
Notice the forEach variable is named p instead of d.
As the data changed, the old data seems to be kept somehow.
Either way, I simply deleted the list before applying the new data:
languages_dom
.selectAll('li')
.remove();
Can’t say this is graceful, nor performant, but it gets the job done :)

D3.js nested data joins on HTML lists and sub-lists

This Fiddle http://jsfiddle.net/kaxjL3La/ shows my attempt to adapt the D3 general update pattern to using nested <ul><li> HTML lists. I believe what complicates this situation is that I'm seeking to use the same tags in the outer and inner lists, unlike examples on the web with tr/td or g/circle.
Explaining the fiddle in words: I want a list of numbers to be placed in an unordered list, with each number having a child list with some derived values:
function update(arr) {
var globalList = d3.select("#lst");
var data = globalList.selectAll("li").data(arr, function(d) { return d; });
data.exit().remove();
var list = data.enter().append('li').text(
function(d, i) { return "[" + i + "]=" + d; });
var subList = list.selectAll("ul")
.data(function(d) { return [ d / 10, d / 100 ]; })
.enter()
.append("ul")
.append("li")
.text(function(d, i) { return "[" + i + "]=" + d; });
}
The first update (creation step) works fine, but subsequent calls to update result in the sub-lists disappearing for the update elements (numbers that are the same between calls to update(), i.e., 30). If you add a console.log(data.exit()) before the remove() call, you see that the selectAll("li").data(...) call has matched the sub-lists, which will then be removed.
If I omit the <li> tags in the sub-lists, and just have a sequence of <ul> tags, as in update2(), then I do not lose the sub-lists on update. This is the correct behavior as far as my data join and data-driven display is concerned, but it is not good HTML to have a sequence of <ul> tags containing my list elements! I only showed this to confirm that, if the sublists consisted of tags that weren't captured by selectAll(), I get correct behavior.
My question is: how do I prevent D3's selectAll from recursing and finding all <li> descendants of the globalList selector so that I can use <ul><li> tags in the sub-lists?
After reading https://stackoverflow.com/a/14438376/500207, I can accomplish this by assigning the outer list's <li> tags a custom CSS class, like outer, then calling selectAll("li.outer").
function update(arr) {
var globalList = d3.select("#lst");
// v----v note 1 (of 2)
var data = globalList.selectAll("li.outer").data(arr, function(d) { return d; });
data.exit().remove();
var list = data.enter().append('li').text(
function(d, i) { return "[" + i + "]=" + d; })
.classed('outer', true); // <--- note 2 (of 2)
var subList = list.selectAll("ul")
.data(function(d) { return [ d / 10, d / 100 ]; })
.enter()
.append("ul")
.append("li")
.text(function(d, i) { return "[" + i + "]=" + d; });
Is this really the best thing to do?

difference between function(d) and function(d,i)?

Every D3js beginner must be going through this thought, I am pretty much sure about it.
I have been around this thing for few hours now!!!!But I don't know how to use it and what is the difference between them?
function(d){return d}
function(d,i){return d and some more custom code}
for Example--->
var data = [4, 8, 15, 16, 23, 42];
Function(d):::::
chart.selectAll("div")
.data(data)
.enter().append("div")
.style("width", function(d) { return d * 10 + "px"; })
.text(function(d) { return d; });
------------------------------------------------------------------------------------
Function(d*i):::::
chart.selectAll("rect")
.data(data)
.enter().append("rect")
.attr("y", function(d, i) { return i * 20; })
.attr("width", x)
.attr("height", 20);
Your example is a good illustrator of the difference between the two.
In the first example, only d is used. d represents the data associated with a given selection. In this case, an array of selected div elements is created, one for each element in the data array:
chart.selectAll("div")
.data(data)
.enter()
.append("div")
This not only creates an array of div elements, but associates data with each of those elements. This is done on a one-to-one basis, with each div corresponding to a single element in the data array. One is associated with '4', one with '8', and so on.
If I then go on to use .text(function(d){...}) on the array of selections, d will refer to the data associated with each selected div, so if I use the following method on my selections:
.text(function(d) { return d; });
Each of my divs will have text added, the value of which is d, or the data associated with the element.
When an array of selections is created, they are also given an index in the array. In your example, this corresponds to the position of their data in the data array. If your function requests both d and i, then i will correspond to this index. Going back to our divs, the div associated with '4' will have an index of '0', '8' will have an index of '1', and so on.
It's also important to note that the character used in the variable requested doesn't matter. The first variable in the function call is always the data, and the second is the index. If i used a method like
.text(function(cat,moose){ return( "data is: " + cat + " index is: " + moose)})
cat will correspond to the data of the selection, and moose will correspond to the index.
I hope that this example can help you. This is a complete web page where you can start playing:
<!doctype html>
<meta charset="utf-8">
<title>my first d3</title>
<body>
<script>
var data=[10,20,30,40];
var lis = d3.select("body")
.append("ul")
.selectAll("li")
.data(data)
lis.enter()
.append("li")
.text(function(d,i){ return "item n° "+i+" has value: "+d})
</script>
Basically d is the value of the data, and i is his index.
You can take a look of this example here: http://jsfiddle.net/M8nK8/
If you're talking about the callback functions you would pass to methods like .attr(), then the function is called for each item in the current selection, where the i gives you the index of the current item, but depending on what you're doing you might not care about the index. So although D3.js will always call your function with both arguments, if you don't actually need the second argument in a particular case your function need not declare it explicitly.
JavaScript lets you call any function with any number of arguments regardless of how many were explicitly included in the function declaration. If you call a function with fewer arguments than were defined the leftovers will get the value undefined. If you call a function with more arguments than were defined you can still access the additional ones from within the function by using the arguments object - or you can ignore them.
(Note: you should have a lowercase f in function().)

Understanding how D3.js binds data to nodes

I'm reading through the D3.js documentation, and am finding it hard to understand the selection.data method from the documentation.
This is the example code given in the documentation:
var matrix = [
[11975, 5871, 8916, 2868],
[ 1951, 10048, 2060, 6171],
[ 8010, 16145, 8090, 8045],
[ 1013, 990, 940, 6907]
];
var tr = d3.select("body").append("table").selectAll("tr")
.data(matrix)
.enter().append("tr");
var td = tr.selectAll("td")
.data(function(d) { return d; })
.enter().append("td")
.text(function(d) { return d; });
I understand most of this, but what is going on with the .data(function(d) { return d; }) section of the var td statement?
My best guess is as follows:
The var tr statement has bound a four-element array to each tr node
The var td statement then uses that four-element array as its data, somehow
But how does .data(function(d) { return d; }) actually get that data, and what does it return?
When you write:
….data(someArray).enter().append('foo');
D3 creates a bunch of <foo> elements, one for each entry in the array. More importantly, it also associates the data for each entry in the array with that DOM element, as a __data__ property.
Try this:
var data = [ {msg:"Hello",cats:42}, {msg:"World",cats:17} ];
d3.select("body").selectAll("q").data(data).enter().append("q");
console.log( document.querySelector('q').__data__ );
What you will see (in the console) is the object {msg:"Hello",cats:42}, since that was associated with the first created q element.
If you later do:
d3.selectAll('q').data(function(d){
// stuff
});
the value of d turns out to be that __data__ property. (At this point it's up to you to ensure that you replace // stuff with code that returns a new array of values.)
Here's another example showing the data bound to the HTML element and the ability to re-bind subsets of data on lower elements:
The key to understanding what this code is doing is to recognize that selections are arrays of arrays of DOM elements. The outer-most array is called a 'selection', the inner array(s) are called 'groups' and those groups contain the DOM elements. You can test this by going into the console at d3js.org and making a selection like d3.selectAll('p'), you will see an array containing an array containing 'p' elements.
In your example, when you first call selectAll('tr') you get a selection with a single group that contains all the 'tr' elements. Then each element of matrix is matched to each 'tr' element.
But when you call selectAll('td') on that selection, the selection already contains a group of 'tr' elements. This time each of those elements will each become a group of 'td' elements. A group is just an array, but it also has a parentNode property that references the old selection, in this case the 'tr' elements.
Now when you call data(function(d) { return d; }) on this new selection of 'td' elements, d represents the data bound to each group's parent node. So in the example, the 'td's in the first group will be bound with the array [11975, 5871, 8916, 2868]. The second group of 'td's are bound with [ 1951, 10048, 2060, 6171].
You can read mike bostock's own excellent explanation of selections and data binding here: http://bost.ocks.org/mike/selection/
Use the counter i to show the index of the data being used.
var tr = d3.select("body").append("table").selectAll("tr")
.data(matrix)
.enter().append("tr") //create a row for each data entry, first index
.text(function(d, i) { return i}); // show the index i.e. d[0][] then d[1][] etc.
var td = tr.selectAll("td")
.data(function(d) { return d; })
.enter().append("td")
.style("background-color", "yellow") //show each cell
.text(function(d,i) { return i + " " + d; }); // i.e d[from the tr][0] then d[from the tr][1]...

Categories

Resources