d3.js Family Tree Spouse Highlight - d3.js

Code link: http://jsfiddle.net/mj58659094/yKUsQ/;
When clicked on a person (node), it selects the spouse also. I only want to select (highlight) the person I clicked on (husband or wife or child). (When I inspect the html in FireBug, spouse nodes (g transform="translate(0,70)") are inside the person nodes. I think they should be outside, but within g class="node" group). I don't know how to fix this. Anyone, please help. Thanks.

Updated: Edit below
I think you are right in that the best way to solve your onclick problem is to keep a person's spouses in the same group as the person (instead of in a nested group). In addition to that, I think you are applying the onclick in the wrong place. My advice is to
Change lines 325-330 to the following
var node = g.append("svg:g").attr("class", "node")
.selectAll("g")
.data(function (d) { return d.nodes; })
.enter().append("svg:g");
node.append("svg:rect")
.on("click", clickedNode);
Currently, you were applying the onclick to the group containing both the person and his/her spouses, while instead you want to use the onclick on each person/spouse separately. Note that once you make this change, you will need to update your code to test each node's rect (instead of the node's group g) as to whether it is selected (lines 355-365). You will also have to update your CSS to style .node rect.normal and .node rect.selected on lines 25 and 29 of your CSS file.
The second issue is that you are only drawing the first spouse for each person. Currently the updateSpouses function is iterating over each person, and then adding a group with a rectangle for just the first spouse. What you need to do is first add a group for each person with spouses, and then over each person's spouses. Here's a rough draft of how to modify updateSpouses to get you started:
var node = svgTree.selectAll(".node g")
.append("svg:g").attr("transform", function (d, i) {
if (i == d3.familyTree.rootGeneration)
return "translate(" + (d.spouseX) + "," + (d3.familyTree.gapBetweenSpouses) + ")";
else
return "translate(" + 0 + "," + (d3.familyTree.gapBetweenSpouses) + ")";
})
.filter(function (d, i) { return personSpouses" in d });
var spouses = node.selectAll(".spouse")
.data(function(d){ return d.personSpouses }).enter()
.append("rect")
.on("click", clickedNode);
Edit
In response to your comment, I went ahead and modified your original code http://jsfiddle.net/mdml/xJBXm/. Here's a quick summary of the changes I made:
I put each person in a group, which then has its own onclick attribute (lines 141-146), so that when you click on the rectangle/text clickedNode is called.
I also put each set of spouses in a group (as described above) so that each of them are individually clickable as well (lines 229-232).
I modified the resetNodePersonSelected and setNodePersonSelected functions so that they search/update spouses in addition to children.
I've described the changes in high-level above but let me know if you have any more questions on the implementation.

Related

D3 data binding [D3js in Action]

I'm new to d3.js, and am working my way through the book "D3.js in action". So far I have been able to figure out all the questions I had, but this one I can't completely answer on my own, it seems.
I post the source code from the book here, since it is available on the books website and the authors homepage. This is the bl.ocks: http://bl.ocks.org/emeeks/raw/186d62271bb3069446b5/
The basis idea of the code is to create a spreadsheet-like layout out of div elements filled with fictious twitter data. Also implemented is a sort function to sort the data by timestamp and reorder the sheet. As well as a function to reestablish the original order.
Here is the code (I left out the part where the table structure is created, except the part where the data is bound):
<html>
<...>
<body>
<div id="traditional">
</div>
</body>
<footer>
<script>
d3.json("tweets.json",function(error,data) { createSpreadsheet(data.tweets)});
function createSpreadsheet(incData) {
var keyValues = d3.keys(incData[0])
d3.select("div.table")
.selectAll("div.datarow")
.data(incData, function(d) {return d.content})
.enter()
.append("div")
.attr("class", "datarow")
.style("top", function(d,i) {return (40 + (i * 40)) + "px"});
d3.selectAll("div.datarow")
.selectAll("div.data")
.data(function(d) {return d3.entries(d)})
.enter()
.append("div")
.attr("class", "data")
.html(function (d) {return d.value})
.style("left", function(d,i,j) {return (i * 100) + "px"});
d3.select("#traditional").insert("button", ".table")
.on("click", sortSheet).html("sort")
d3.select("#traditional").insert("button", ".table")
.on("click", restoreSheet).html("restore")
function sortSheet() {
var dataset = d3.selectAll("div.datarow").data();
dataset.sort(function(a,b) {
var a = new Date(a.timestamp);
var b = new Date(b.timestamp);
return a>=b ? 1 : (a<b ? -1 : 0);
})
d3.selectAll("div.datarow")
.data(dataset, function(d) {return d.content})
.transition()
.duration(2000)
.style("top", function(d,i) {return (40 + (i * 40)) + "px"});
}
function restoreSheet() {
d3.selectAll("div.datarow")
.transition()
.duration(2000)
.style("top", function(d,i) {return (40 + (i * 40)) + "px"});
}
}
</script>
</footer>
</html>
What I don't fully understand is how sortSheet and restoreSheet work.
This part of sortSheet looks like it rebinds data, but after console logging I think it doesn't actually rebind data to the DOM. Instead it just seems to redraw the div.tablerow elements based on the array index of the sorted array.
But then what purpose does the key-function have?
And why is the transition working? How does it know which old element to put in which new position?
EDIT:
---After some more reading I now know that selectAll().data() does indeed return the update selection. Apparenty the already bound data identified by the key function is re-sorted to match the order of the keys in the new dataset? Is that correct?
So the update selection contains the existing div.datarow s, but in a new ordering. The transition() function works on the new order, drawing the newly ordered div.datarow s beginning with index 0 for the first element to determine its position on the page, to index n for the last element. The graphical transition then somehow (how? by way of the update selection?) knows where the redrawn div.datarow was before and creates the transition-effect.
Is that correct so far?---
d3.selectAll("div.datarow")
.data(dataset, function(d) {return d.content}) //why the key function?
.transition()
.duration(2000)
.style("top", function(d,i) {return (40 + (i * 40)) + "px"});
And what happens when the original order is restored? Apparently during both operations there is no actual rebinding of data, and the order of the div.datarows in the DOM does not change. So the restore function also redraws the layout based on the array index.
But what kind of selection does the .transition() work on? Is it an update? It is an update.
And why does the drawing using the index result in the old layout? Shouldn't the index of the DOM elements always be 0,1,...,n? I think it is. Apparently the old page layout is redrawn, with the DOM never having changed. But how can the transition() function create the appropriate graphical effect?
function restoreSheet() {
d3.selectAll("div.datarow")
.transition()
.duration(2000)
.style("top", function(d,i) {return (40 + (i * 40)) + "px"});
}
I have been thinking for hours about this, but I can't find the correct answer I think.
Thanks for your help!
It all becomes clear when you understand where all these functions were called: inside the json function, where the data was originally bound. When a button calls the sortSheet function, a new array of objects is made and bound to the rows. The transition simply starts with the original order and move the rows according to the new order of the objects inside the array.
And what happens when the original order is restored?
Now comes the interesting part: restoreSheet is called inside the json function and has no access to the dataset variable. So, the data restoreSheet uses is the original data. Then, a transition simply moves the rows according to the order of the objects inside the original array.
I just made a fiddle replicating this: https://jsfiddle.net/k9012vro/2/
Check the code: I have an array with the original data. Then, a button called "sort" creates a new array.
When I click "original" the rectangles move back to the original position. But there is nothing special in that function, no new data being bound:
d3.select("#button1").on("click", function(){
rects.transition()
.duration(500).attr("x", function(d, i){ return i * 30})
});
It moves all the rectangles to the original positions because this function uses the same original data.

How to display label for bi-direct link/path/line in D3js force directed layout

I'm trying to draw a network topology using D3sj force directed layout. I did a basic topology with link and node. However, now i want to show the interface name for each link on each node as the following picture. Could you guys guide me how i can do that?
Thank you in advance!
P/S I attached my topology here!1
Ideally you should present the javascript that you have already written, and explained what it does do, and what is missing. Having said that, I recently finished work on a similar project to what you describe, so had the following results handy.
Does this jsfiddle do what you are attempting?
There are two key components. The first is in defining the text elements (here I append them to an SVG element):
var text = svg.selectAll('text')
.data(force.nodes())
.enter().append('text')
.text(function (d) { return d.name });
Here I'm assuming that the nodes[] array contains objects with a .name property that is to be displayed.
The second component is to translate the text elements to their appropriate positions, inside the tick handler:
function tick () {
text.attr('transform', function (d) {
return 'translate(' + d.x + ',' + d.y + ')';
});
// Other code for nodes and links
}
See the jsfiddle for a complete working example, including commented code that should allow you to add images at the nodes if you want to try to reproduce your sample image more closely.

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.

How to properly add and use D3 Events?

I'm having trouble understanding using D3 events and dispatch functions. I have a chart example that I've been working on called: "Vertical Bar Charts With Legends."
Drawing the charts and the legends was easy enough but I'd like to add the ability to highlight each bar as I mouseover its correlating text legend, located to the right of the chart.
I've read through all of the event documentation and even looked at a number of examples, most of which are pretty complicated, but I seem to be missing something. Would anyone know how to best accomplish the text legend mouseover functionality that dispatches events to automatically change colors of the corresponding vertical bars?
This question is similar to the one you posted in the d3-js Google Group. Without duplicating what I wrote there, I would reiterate that you probably don't want d3.dispatch; that is intended for custom event abstractions (such as brushes and behaviors). It'll be simpler to use native events.
If you want your legend to change the color of the corresponding bar on mouseover, then breakdown the problem into steps:
Detect mouseover on the legend.
Select the corresponding bar.
Change the bar's fill color.
First, use selection.on to listen for "mouseover" events on the legend elements. Your listener function will be called when the mouse goes over a legend element, and will be called with two arguments: the data (d) and the index (i). You can use this information to select the corresponding bar via d3.select. Lastly, use selection.style to change the "fill" style with the new color.
If you're not sure how to select the corresponding bar on legend mouseover, there are typically several options. The most straightforward is to select by index, assuming that the number of legend elements and number of rect elements are the same, and they are in the same order. In that case, if a local variable rect contains the rect elements, you could say:
function mouseover(d, i) {
d3.select(rect[0][i]).style("fill", "red");
}
If you don't want to rely on index, another option is to scan for the matching bar based on identical data. This uses selection.filter:
function mouseover(d, i) {
rect.filter(function(p) { return d === p; }).style("fill", "red");
}
Yet another option is to give each rect a unique ID, and then select by id. For example, on initialization, you could say:
rect.attr("id", function(d, i) { return "rect-" + i; });
Then, you could select the rect by id on mouseover:
function mouseover(d, i) {
d3.select("#rect-" + i).style("fill", "red");
}
The above example is contrived since I used the index to generate the id attribute (in which case, it's simpler and faster to use the first technique of selecting by index). A more realistic example would be if your data had a name property; you could then use d.name to generate the id attribute, and likewise select by id. You could also select by other attributes or class, if you don't want to generate a unique id.
Mike's answer is great.
I used it come up with this for selecting a cell in a grid I was drawing:
.on('click', (d, i) ->
console.log("X:" + d.x, "Y:" + d.y) #displays the cell x y location
d3.select(this).style("fill", "red");
So when I am entering the data in I added the event listener and using d3.select(this).
See the code in context below:
vis.selectAll("rect")
.data(singleArray)
.enter().append("svg:rect")
.attr("stroke", "none")
.attr("fill", (d) ->
if d.lifeForm
return "green"
else
return "white")
.attr("x", (d) -> xs(d.x))
.attr("y", (d) -> ys(d.y))
.attr("width", cellWidth)
.attr("height", cellHeight)
.on('click', (d, i) ->
console.log("X:" + d.x, "Y:" + d.y)
d3.select(this).style("fill", "red");
return
)

Resources