D3.js .append( ) on existing <div> and hierarchy - d3.js

According to my code, it looks like key_square and key_line divisions should be sibling elements to key_row since I am appending them to the division "key", but they are descendants of key_row.
Could someone explain this to me?
Thank you,
<body>
<div id="timeseries">
</div>
<div id="key">
</div>
<script>
var key = d3.select("#key")
.selectAll("div")
.data(data)
.enter()
.append("div")
.attr("class","key_row")
.attr("id",function (d){ return d.line_id;})
//add square
key.append("div")
.attr("class", "key_square")
.style("background-color","#C8DAF8")
//d3.select("#key")
key.append("div")
.attr("class","key_id")
.attr("id","key_line")
.style("background-color","#E9F0FC")
.text(function (d) { return d.line_id;});

Let us discuss the code one by one,
first one
var key = d3.select("#key")
.selectAll("div")
.data(data)
.enter()
.append("div")
.attr("class","key_row")
.attr("id",function (d){ return d.line_id;})
In the above code we have selected the div using id, appending divs here the number of divs are created according to the length of the data and then we are specifying the class and ids to these divs, after execution of these code d3 will returns an array containing these divs, so now the variable key holds these newly created divs. If you want to see that write one console.log(key) stmt after above code.
second
key.append("div")
.attr("class", "key_square")
.style("background-color","#C8DAF8")
Here we are appending divs to key, means this append function is called on one one div which are holding by key, so after execution of this code divs are created inside as descendant to above created divs.
third
key.append("div")
.attr("class","key_id")
.attr("id","key_line")
.style("background-color","#E9F0FC")
.text(function (d) { return d.line_id;});
Here also the same thing we are again calling append function on Initially created divs, so the same thing will happens here too. divs are added as descendant to initially created divs.
So that's how it works.
This much I can say.
If any where made a mistake point it.
Thanks

Related

D3.js v6.2 - get data index in listener function - selection.on('click', listener)

In previous versions it was possible to retrieve the index i by:
selection.on("click", function(d,i){...}
However, that does not seem to work anymore in the latest version since the first parameter is always the event object. How can I get the index of the datum in the listener function?
let data = [2,5,8,9]
d3.select("body").selectAll("p")
.data(data)
.enter()
.append("p")
.text(d=>d)
.on("mouseover", function(e,d,i){
//console.log(e); --> event
console.log(d);
console.log(i);
// i should be the index of the hovered element
})
<script src="https://d3js.org/d3.v6.min.js"></script>
When a specified event is dispatched on a selected element, the specified listener will be evaluated for the element, being passed the current event (event) and the current datum (d), with this as the current DOM element (event.currentTarget).
Official documentation: https://github.com/d3/d3-selection/blob/v2.0.0/README.md#selection_on
The Observable notebook on the new features in d3-selection 2.0 suggests using a local variable to preserve the index:
Listeners are no longer passed the selected element’s index (i) or siblings (nodes). These were rarely used, had confusing behavior if elements were re-selected, and could cause memory leaks. If you need them, bake them into the element’s data or use a local variable instead.
This can be done with little effort along the following lines:
let data = [2,5,8,9]
const index = d3.local(); // Local variable for storing the index.
d3.select("body").selectAll("p")
.data(data)
.enter()
.append("p")
.text(d=>d)
.each(function(d, i) {
index.set(this, i); // Store index in local variable.
})
.on("mouseover", function(e,d,i){
console.log(d);
console.log(index.get(this)); // Get index from local variable.
});
<script src="https://d3js.org/d3.v6.min.js"></script>
Note, that although the above example uses a separate call to .each() for clarity this could also be done in the existing call to .text() for sake of brevity and performance.
I'm afraid the index was removed in this update. If you're looking for one, the easiest way to get it is to append it to the data as a property (.data(data.map((d,i) => ({ value: d, i: i }))) for example) or look up the index in the selection of nodes that you have:
let data = [2,5,8,9]
const p = d3.select("body").selectAll("p")
.data(data)
.enter()
.append("p")
.text(d=>d)
.on("mouseover", function(e,d){
console.log(d);
console.log(p.nodes().indexOf(this));
})
<script src="https://d3js.org/d3.v6.min.js"></script>

How to add an list item to an existing list of items in D3?

Newbie here. I am trying to add an li to an existing list but not getting expected result. I would like to add it to the end of list.
...
<ul id="items">
<li class="item">One</li>
<li class="item">Two</li>
<li class="item">Three</li>
<li class="item">Four</li>
<li class="item">Five</li>
</ul>
...
In console
d3.selectAll('li')
.data([1,2,3,4,5,6])
.enter()
.append('li')
.text('a text')
.classed('item',true);
This adds li to the end of body
...
</body>
<li class="item">a text</li>
...
Update:
Did a bit of experiment and got a working one. However, need to understand why the earlier one didn't work?
d3.select('#items')
.selectAll('.item')
.data([1,2,3,4,5,6])
.enter()
.append('li')
.text('a text')
.classed('item',true);
In your first attempt you weren't providing D3 with an element to append to so it just appended to the end of the document. By selecting ul#items and then appending to it you were successful in adding one additional li as you bound your data array to all li children of ul#items. As you only had 5 li items and 6 items in your data array the enter() method was called which appended an extra li. Try this code and consider what is happening:
var data = [1,2,3,4,5];
function updateData(data) {
var item = d3.select('#items')
.selectAll('li')
.data(data);
// Enter
item
.enter()
.append('li')
.attr('class', 'item')
.text(function(d) { return 'List item ' + d });
// Update
item
.text(function(d) { return 'Updated value to' + d });
// Exit
item.exit().remove();
}
updateData(data);
setTimeout(function() {
var newData = data.map(function(v) {
return v * 2;
});
updateData(newData)
}, 3000);
setTimeout(function() {
var newData = data.concat(data);
updateData(newData)
}, 6000);
setTimeout(function() {
updateData(data)
}, 9000);
We can select the ul#items and bound data to it. When updateData is called D3 will assess whether there are more dom elements than data elements and choose whether to call enter() or exit() accordingly. We also have an update procedure (to change the text to the new value it has been given). Hopefully this explains the nature of what you were experiencing. Have a good read about enter, update, exit in D3. Once you've understood that you can really do a lot of complex stuff with ease.

D3JS Circle on top of each other [duplicate]

What is an effective way to bring an SVG element to the top of the z-order, using the D3 library?
My specific scenario is a pie chart which highlights (by adding a stroke to the path) when the mouse is over a given piece. The code block for generating my chart is below:
svg.selectAll("path")
.data(d)
.enter().append("path")
.attr("d", arc)
.attr("class", "arc")
.attr("fill", function(d) { return color(d.name); })
.attr("stroke", "#fff")
.attr("stroke-width", 0)
.on("mouseover", function(d) {
d3.select(this)
.attr("stroke-width", 2)
.classed("top", true);
//.style("z-index", 1);
})
.on("mouseout", function(d) {
d3.select(this)
.attr("stroke-width", 0)
.classed("top", false);
//.style("z-index", -1);
});
I've tried a few options, but no luck so far. Using style("z-index") and calling classed both did not work.
The "top" class is defined as follows in my CSS:
.top {
fill: red;
z-index: 100;
}
The fill statement is there to make sure I knew it was turning on/off correctly. It is.
I've heard using sort is an option, but I'm unclear on how it would be implemented for bringing the "selected" element to the top.
UPDATE:
I fixed my particular situation with the following code, which adds a new arc to the SVG on the mouseover event to show a highlight.
svg.selectAll("path")
.data(d)
.enter().append("path")
.attr("d", arc)
.attr("class", "arc")
.style("fill", function(d) { return color(d.name); })
.style("stroke", "#fff")
.style("stroke-width", 0)
.on("mouseover", function(d) {
svg.append("path")
.attr("d", d3.select(this).attr("d"))
.attr("id", "arcSelection")
.style("fill", "none")
.style("stroke", "#fff")
.style("stroke-width", 2);
})
.on("mouseout", function(d) {
d3.select("#arcSelection").remove();
});
As explained in the other answers, SVG does not have a notion of a z-index. Instead, the order of elements in the document determines the order in the drawing.
Apart from reordering the elements manually, there is another way for certain situations:
Working with D3 you often have certain types of elements that should always be drawn on top of other types of elements.
For example, when laying out graphs, links should always be placed below nodes. More generally, some background elements usually need to be placed below everything else, while some highlights and overlays should be placed above.
If you have this kind of situation, I found that creating parent group elements for those groups of elements is the best way to go. In SVG, you can use the g element for that. For example, if you have links that should be always placed below nodes, do the following:
svg.append("g").attr("id", "links")
svg.append("g").attr("id", "nodes")
Now, when you paint your links and nodes, select as follows (the selectors starting with # reference the element id):
svg.select("#links").selectAll(".link")
// add data, attach elements and so on
svg.select("#nodes").selectAll(".node")
// add data, attach elements and so on
Now, all links will always be appended structurally before all node elements. Thus, the SVG will show all links below all nodes, no matter how often and in what order you add or remove elements. Of course, all elements of the same type (i.e. within the same container) will still be subject to the order in which they were added.
One of the solutions presented by the developer is: "use D3's sort operator to reorder the elements." (see https://github.com/mbostock/d3/issues/252)
In this light, one might sort the elements by comparing their data, or positions if they were dataless elements:
.on("mouseover", function(d) {
svg.selectAll("path").sort(function (a, b) { // select the parent and sort the path's
if (a.id != d.id) return -1; // a is not the hovered element, send "a" to the back
else return 1; // a is the hovered element, bring "a" to the front
});
})
Since SVG doesn't have Z-index but use the order of the DOM elements, you can bring it to front by:
this.parentNode.appendChild(this);
You can then e.g. make use of insertBefore to put it back on mouseout. This however requires you to be able to target the sibling-node your element should be inserted before.
DEMO: Take a look at this JSFiddle
The simple answer is to use d3 ordering methods. In addition to d3.select('g').order(), there is .lower() and .raise() in version 4. This changes how your elements appear. Please consult the docs for more information - https://github.com/d3/d3/blob/master/API.md#selections-d3-selection
SVG doesn't do z-index. Z-order is dictated by the order of the SVG DOM elements in their container.
As far as I could tell (and I've tried this a couple of times in the past), D3 doesn't provide methods for detaching and reattaching a single element in order to bring it to the front or whatnot.
There is an .order() method, which reshuffles the nodes to match the order they appear in the selection. In your case, you need to bring a single element to the front. So, technically, you could resort the selection with the desired element in front (or at the end, can't remember which is topmost), and then call order() on it.
Or, you could skip d3 for this task and use plain JS (or jQuery) to re-insert that single DOM element.
I implemented futurend's solution in my code and it worked, but with the large number of elements I was using, it was very slow. Here's the alternative method using jQuery that worked faster for my particular visualization. It relies on the svgs you want on top having a class in common (in my example the class is noted in my data set as d.key). In my code there is a <g> with the class "locations" that contains all of the SVGs I'm re-organizing.
.on("mouseover", function(d) {
var pts = $("." + d.key).detach();
$(".locations").append(pts);
});
So when you hover on a particular data point, the code finds all the other data points with SVG DOM elements with that particular class. Then it detaches and re-inserts the SVG DOM elements associated with those data points.
Wanted to expand on what #notan3xit answered rather than write out an entire new answer (but I don't have enough reputation).
Another way to solve the element order problem is to use 'insert' rather than 'append' when drawing . That way the paths will always be placed together before the other svg elements(this assumes your code already does the enter() for links before the enter() for the other svg elements).
d3 insert api: https://github.com/mbostock/d3/wiki/Selections#insert
It took me ages to find how to tweak the Z-order in an existing SVG. I really needed it in the context of d3.brush with tooltip behavior. In order to have the two features work nicely together (http://wrobstory.github.io/2013/11/D3-brush-and-tooltip.html), you need the d3.brush to be the first in Z-order (1st to be drawn on the canvas, then covered by the rest of the SVG elements) and it will capture all mouse events, no matter what is on top of it (with higher Z indices).
Most forum comments say that you should add the d3.brush first in your code, then your SVG "drawing" code. But for me it was not possible as I loaded an external SVG file. You can easily add the brush at any time and alter the Z-order later on with:
d3.select("svg").insert("g", ":first-child");
In the context of a d3.brush setup it will look like:
brush = d3.svg.brush()
.x(d3.scale.identity().domain([1, width-1]))
.y(d3.scale.identity().domain([1, height-1]))
.clamp([true,true])
.on("brush", function() {
var extent = d3.event.target.extent();
...
});
d3.select("svg").insert("g", ":first-child");
.attr("class", "brush")
.call(brush);
d3.js insert() function API: https://github.com/mbostock/d3/wiki/Selections#insert
Hope this helps!
You can Do like this On Mouse Over You can Pull it to top.
d3.selection.prototype.bringElementAsTopLayer = function() {
return this.each(function(){
this.parentNode.appendChild(this);
});
};
d3.selection.prototype.pushElementAsBackLayer = function() {
return this.each(function() {
var firstChild = this.parentNode.firstChild;
if (firstChild) {
this.parentNode.insertBefore(this, firstChild);
}
});
};
nodes.on("mouseover",function(){
d3.select(this).bringElementAsTopLayer();
});
If You want To Push To Back
nodes.on("mouseout",function(){
d3.select(this).pushElementAsBackLayer();
});
Version 1
In theory, the following should work fine.
The CSS code :
path:hover {
stroke: #fff;
stroke-width : 2;
}
This CSS code will add a stroke to the selected path.
The JS code :
svg.selectAll("path").on("mouseover", function(d) {
this.parentNode.appendChild(this);
});
This JS code first removes the path from the DOM tree and then adds it as the last child of its parent. This makes sure the path is drawn on top of all other children of the same parent.
In practice, this code works fine in Chrome but breaks in some other browsers. I tried it in Firefox 20 on my Linux Mint machine and couldn't get it to work. Somehow, Firefox fails to trigger the :hover styles and I haven't found a way to fix this.
Version 2
So I came up with an alternative. It may be a bit 'dirty', but at least it works and it doesn't require looping over all elements (as some of the other answers).
The CSS code :
path.hover {
stroke: #fff;
stroke-width : 2;
}
Instead of using the :hover pseudoselector, I use a .hover class
The JS code :
svg.selectAll(".path")
.on("mouseover", function(d) {
d3.select(this).classed('hover', true);
this.parentNode.appendChild(this);
})
.on("mouseout", function(d) {
d3.select(this).classed('hover', false);
})
On mouseover, I add the .hover class to my path. On mouseout, I remove it.
As in the first case, the code also removes the path from the DOM tree and then adds it as the last child of its parent.
I solved it by using the raise function.
const raise = (d) => {
d3.select(d).raise()
}
And in the component that you need to raise on hover (along with all its children, just place this.
.on("mouseover", (d) => raise(d.srcElement.parentNode))
Depending on your structure, maybe parentNode is not needed. In this example they used "this" but that didn't work in React. https://codepen.io/_cselig/pen/KKgOppo

How to append child divs to parent divs by selecting on ids from a data file

I have html in this structure:
<div id="typeA"></div>
<div id="typeB"></div>
and a table of data such as:
[["#typeA","someValue"],
["#typeA","anotherValue"],
["#typeB","thirdValue"]]
and I think it's possible to use d3.js to append the values as text inside child div of each of the present divs like so:
<div id="typeA">
<div>someValue</div>
<div>anotherValue</div>
</div>
<div id="typeB">
<div>thirdValue</div>
</div>
Would this need a nest()?
fyi this didn't work for reasons that are now obvious to me:
var body = d3.select("body")
.data(matrix)
.enter()
.select(function(d) {return d[0]}).
.data(this)
.enter()
.append("div")
.text(function(d) {return d[1]});
D3 isn't ideal in this case because your document is not entirely data-driven -- you already have elements that need to be matched to the data in a non-D3 fashion. You can however emulate what D3 does and "bind" the data to the existing elements manually:
data.forEach(function(d) {
var elem = d3.select(d[0]).node();
if(elem.__data__ === undefined) elem.__data__ = [];
elem.__data__.push(d[1]);
});
This will bind the array of corresponding data items to each DOM element. Now you can use D3's nested selections to create the nested elements:
d3.selectAll("div").selectAll("div")
.data(function(d) { return d; })
.enter().append("div")
.html(function(d) { return d; });
Note the double .selectAll("div"); the first to select the existing elements and the second to select the (yet not existent) nested elements.
Complete demo here.
use jquery istead it's much more supported and performant.
in Jquery you just have a look to traversing in
this pretty good reference good reference :

Link D3 force layout network diagram with table on mouseover

I took Michael Bostock's forced network example of a molecule, http://bl.ocks.org/mbostock/3037015
I added a table of the data following the example in http://www.d3noob.org/2013/02/add-html-table-to-your-d3js-graph.html.
I added separate mouseover events to the network and the table. If I mouseover a node in the diagram, the selected node is highlighted in orange.
var node = svg.selectAll(".node")
.data(graph.nodes)
.enter().append("g")
.attr("class", "node")
.on("mouseover", function() {
d3.select(this).select("circle").style("stroke-width", 6 );
d3.select(this).select("circle").style("stroke", "orange");
d3.select(this).select("text").style("font", "20px sans-serif");
})
.on("mouseout", function() {
d3.select(this).select("circle").style("stroke-width", 1.5);
d3.select(this).select("circle").style("stroke", "gray" );
d3.select(this).select("text").style("font", "12px sans-serif");
})
.call(force.drag)
;
And if I mouseover a row in the table, the selected row is highlighted in orange.
var rows = tbody.selectAll("tr")
.data(data)
.enter().append("tr")
.on('mouseover', function(){d3.select(this).style('background-color', 'orange');})
.on('mouseout', function(){d3.select(this).style('background-color', 'white');})
;
I would like to link the mouseover highlighting of the diagram and the table such that:
If I mouse over a node in the diagram, the selected node and its corresponding row in the table are both highlighted.
If I mouse over a row in the table, the selected row and its corresponding node in the diagram are both highlighted.
I haven't been able to find an example of a mouseover-linked diagram and table in D3. Can you point me to one? Or suggest a solution?
I am using local copies of D3 (d3.v3.js) and the data (graph.json) and viewing the diagrams on Firefox 20.0.1 for Windows.
Thanks for your help.
A simple way to do this is to use dom element classes, selections and the data objects that your mouse events will have. Have a look at a working demo here
The pertinent parts:
I am linking the rectangles to the circles based on a join between "class" and "cat" in their two respective datasets. To get at the elements quickly I will incorporate those items into the class attribute:
var table = svg.selectAll('rect').data(data).enter().append('rect');
table.attr(...)
.attr('class', function(d) { return d["class"];} );
You can attach a class to any element, also you can attach more than one class to each element (it is considered a space-delimited list)
I do the same for each circle. Now in the circle mouseover I add:
balls.on("mouseover", function(d) {
d3.select(this).attr("fill","#ffeeee");
d3.selectAll("rect." + d.cat)
.attr('stroke','red')
.attr('stroke-width', 3);
})
D3 passes the data object of the moused-over element, so I grab it's "cat" and use that with a D3 selector on rect's with that class. Like jQuery "rect.A" will select all "rect" elements with class "A"
For a different approach that uses brushes and selects ranges more than elements you can also check out crossfilter whose landing page has a great full example.

Resources