70

First of all, I am using d3.js to display different sized circles in arrays. On mouse over, I want the circle being moused over to become bigger, which I can do, but I have no idea how to bring it to the front. Currently, once it's rendered, it's hidden behind multiple other circles. How can I fix this?

Here's a code sample:

   .on("mouseover", function() { 
    d3.select(this).attr("r", function(d) { return 100; })
  })

I tried using the sort and order methods, but they didn't work. I'm pretty sure i didn't do it correctly. Any thoughts?

areke
  • 1,093
  • 1
  • 14
  • 23
  • I'm still new to d3.js, but I think you should be able to accomplish this by hiding/showing the element in order to re-render it? – Ian Burnette Jan 05 '13 at 02:42
  • From this post it seems that re-appending the element to the svg has this effect. http://stackoverflow.com/questions/482115/with-javascript-can-i-change-the-z-index-layer-of-an-svg-g-element – Ian Burnette Jan 05 '13 at 02:47
  • It's not working for me :\ – areke Jan 05 '13 at 03:27
  • Mike demonstrated a few of ways of doing this in this thread: https://groups.google.com/d/msg/d3-js/OqD9_puVTfg/aMDYbGHB2fAJ – nautat Jan 05 '13 at 05:32
  • 9
    d3.selection.prototype.moveToFront = function() { return this.each(function() { this.parentNode.appendChild(this); }); }; And then you can say selection.moveToFront(). – nautat Jan 05 '13 at 05:33
  • 1
    See how Ian uses this method in this tributary entry: http://enjalot.com/tributary/3651456/ – nautat Jan 05 '13 at 05:36
  • 2
    [Fiddle here](http://jsfiddle.net/mccannf/Lq4zd/3/) using the moveToFront approach. – mccannf Jan 06 '13 at 00:16

4 Answers4

124

TL;DR

With latest versions of D3, you can use selection.raise() as explained by tmpearce in its answer. Please scroll down and upvote!


Original answer

You will have to change the order of object and make the circle you mouseover being the last element added. As you can see here: https://gist.github.com/3922684 and as suggested by nautat, you have to define the following before your main script:

d3.selection.prototype.moveToFront = function() {
  return this.each(function(){
    this.parentNode.appendChild(this);
  });
};

Then you will just have to call the moveToFront function on your object (say circles) on mouseover:

circles.on("mouseover",function(){
  var sel = d3.select(this);
  sel.moveToFront();
});

Edit: As suggested by Henrik Nordberg it is necessary to bind the data to the DOM by using the second argument of the .data(). This is necessary to not lose binding on elements. Please read Henrick's answer (and upvote it!) for more info. As a general advice, always use the second argument of .data() when binding data to the DOM in order to leverage the full performance capabilities of d3.


Edit: As mentioned by Clemens Tolboom, the reverse function would be:

d3.selection.prototype.moveToBack = function() {
    return this.each(function() {
        var firstChild = this.parentNode.firstChild;
        if (firstChild) {
            this.parentNode.insertBefore(this, firstChild);
        }
    });
};
Christopher Chiche
  • 15,075
  • 9
  • 59
  • 98
  • 6
    I needed the reverse too: this is what I did: `d3.selection.prototype.moveToBack = function() { return this.each(function() { var firstChild = this.parentNode.firstChild; if (firstChild) { this.parentNode.insertBefore(this, firstChild); } }); };` – Clemens Tolboom Oct 25 '13 at 13:09
  • 2
    See @Henrik Nordberg answer below. Your data will get out of sync doing this – David Wilton Oct 06 '15 at 01:28
  • Hi @christopher Chiche, I tried your solution here: http://stackoverflow.com/questions/33000725/how-to-position-nvd3-line-graph-above-all-other-area-bar-graphs but still can't get the positions of the line graph to change. Thoughts? – Leon Gaban Oct 07 '15 at 19:49
  • doesnt work in IE if you have other event handlers on node getting moved... they get dropped – tsiorn Nov 05 '15 at 17:32
38

As of d3 version 4, there are a set of built in functions that handle this type of behavior without needing to implement it yourself. See the d3v4 documentation for more info.

Long story short, to bring an element to the front, use selection.raise()

selection.raise()

Re-inserts each selected element, in order, as the last child of its parent. Equivalent to:

selection.each(function() {
this.parentNode.appendChild(this);
});

Community
  • 1
  • 1
tmpearce
  • 12,523
  • 4
  • 42
  • 60
25

If you use the moveToFront() method, make sure you are specifying the second argument to the data() join method, or your data will be out of synch with your DOM.

d3.js joins your data (provided to a parse()) method with DOM elements you create. If you manipulate the DOM as proposed above, d3.js won't know what DOM element corresponds to what data 'point' unless you specify a unique value for each data 'point' in the data() call as the API reference shows:

.data(data, function(d) { return d.name; })

Henrik Nordberg
  • 251
  • 3
  • 4
13

From my painful experience with IE9, using parentNode.appendChild may lead to lost event handlers in the nodes. So I tend to use another approach, that is, sorting the nodes so that the selected one is above the others:

     .on("mouseover", function(selected) {
        vis.selectAll('.node')
        .sort(function(a, b) {
          if (a.id === selected.id) {
            return 1;
          } else {
            if (b.id === selected.id) {
              return -1;
            } else {
              return 0;
            }
          }
        });
      })
Ilya Boyandin
  • 3,069
  • 25
  • 23
  • 1
    can you elaborate here a little bit.. what is vis in your code. And is 'node' a class of your svg elements? I am running into the same issue on IE – tryurbest May 14 '14 at 23:23
  • vis is a D3 selection of the parent element containing the elements which you want to reorder. 'node' is the CSS class which these elements should have. – Ilya Boyandin May 15 '14 at 12:42
  • terser sorting function : `function(a,b) {s = selected.id; return (a.id == s) - (b.id == s);}` – Ben Southgate Oct 14 '14 at 18:08
  • Great solution for IE9. – Robin Wieruch Nov 26 '14 at 13:56
  • how do I get a reference to `vis` from outside the mouseover event? I'm trying to find `vis` inside the `dragstart` event but I can't find it anywhere. `this` references the element that I want to bring to the top. – isapir Jun 30 '15 at 15:21