72

I'm trying to learn D3 by experimenting with one of their basic bubblecharts. First task: figure out how to drag an bubble and have it become the topmost object while it's being dragged. (The problem is getting D3's object model to map onto the DOM, but I'll get there...)

To drag it, we can simply invoke d3's drag behavior using the code they provide:

var drag = d3.behavior.drag()
    .on("dragstart", dragstart)
    .on("drag", dragmove)
    .on("dragend", dragend);

Works great. Drags well. Now, how do we get it to be the topmost item? Search for "svg z-index" here and it becomes quickly apparent that the only way to change the index is to move an object further down in the DOM. OK. They don't make it easy because the individual bubbles don't have ID's, but messing around with the console, we can locate one of those bubble objects with:

$("text:contains('TimeScale')").parent()

and we can move it to the end of the containing svg element with:

.appendTo('svg')

Drag it after you do that, and it's the top-most item. So far, so good, if you're working entirely within the DOM.

BUT: what I really want to do is have that happen automatically whenever a given object/bubble is being dragged. D3 provides a model for dragstart() and dragend() functions that will allow us to embed a statement to do what we want during the dragging process. And D3 provides the d3.select(this) syntax that allows us to access d3's object representation of the object/bubble you're currently dragging. But how do I cleanly turn that massive array they return into a reference to a DOM element that I can interact with and - for instance - move it to the end of the svg container, or perform other references in the DOM, such as form submission?

ZachB
  • 13,051
  • 4
  • 61
  • 89
XML
  • 19,206
  • 9
  • 64
  • 65

3 Answers3

206

You can also get at the DOM element represented by a selection via selection.node() method

var selection = d3.select(domElement);

// later via the selection you can retrieve the element with .node()
var elt = selection.node();
Tom Dunn
  • 2,425
  • 2
  • 13
  • 9
  • 21
    This is a way better answer. – jstaab Apr 15 '14 at 17:55
  • 7
    Btw. this is also the recommended way of d3.js' author Mike Bostok in his article [How Selections Work](http://bost.ocks.org/mike/selection/) – rmoestl Jun 17 '15 at 13:12
  • 1
    Note that regardless of the number of elements in the selection, `node()` will always return just a single DOM element, which will be the node of the first element in the selection. – BallpointBen Jul 15 '20 at 14:16
32

Any DOM element in an SVG document will have an ownerSVGElement property that references the SVG document it is in.

D3's selections are just nested arrays with extra methods on them; if you have .select()ed a single DOM element, you can get it with [0][0], for example:

var foo = d3.select(someDOM);

// later, where you don't have someDOM but you do have foo
var someDom = foo[0][0];
var svgRoot = someDom.ownerSVGElement;

Note, however, that if you are using d3.select(this) then this already is the DOM element; you don't need to wrap it in an D3 selection just to unwrap it.

Phrogz
  • 296,393
  • 112
  • 651
  • 745
  • 3
    Oh, Wow. That's the ticket. I was stuck on trying to use `d3.select(this)`, rather than just `this`. Hence I can dramatically simplify `document.getElementsByTagName("svg")[0].appendChild(document.getElementById(d3.select(this).attr('id')));` into just `$("svg").append($(this));`. Thanks! – XML Apr 27 '12 at 16:20
  • Or just `this.ownerSVGElement.appendChild(this);` – Phrogz Apr 27 '12 at 16:21
  • Perfect! Thank you: I was already trying to ditch the Jquery involvement, but was getting hung up on D3 syntax. Is there a streamlined API for it anywhere? I'm working my way through the documentation, but it seems very scattered, as yet... – XML Apr 27 '12 at 20:51
  • I'm not deep in the D3 community and resources, so I'm not sure. I know that a) There are a LOT of examples scattered and semi-aggregated in many areas. b) The [official API documentation](https://github.com/mbostock/d3/wiki/API-Reference) is thorough, but some things about the presentation throw me off, and so c) I'm in the process of creating [my own view on the documentation](http://objjob.phrogz.net/d3/hierarchy) (currently incomplete). – Phrogz Apr 27 '12 at 21:01
  • 6
    This doesn't seem to work, at least not any more in d3 4.0. Even though there is one element in my selection, `mySel[0]` is `undefined` for me. `mySel._groups[0][0]` returns the DOM element, but this doesn't look like it should be accessed. The `mySel.node()` solution from [the other answer](http://stackoverflow.com/a/22024786/1430156) looks like a cleaner approach. – O. R. Mapper Sep 16 '16 at 08:44
13

You can assign IDs and classes to the individual elements if you want when you append:

node.append("circle.bubble")
.attr("r", function(d) { return d.r; })
.style("fill", function(d) { return fill(d.packageName); })
.attr("id", function(d, i) { return ("idlabel_" + i)})
.attr("class", "bubble")
;

And then you can select by class with selectAll("circle.bubble") or select by id and modify attributes like so:

var testCircle = svg.select("#idlabel_3")
.style("stroke-width", 8);
Elijah
  • 4,609
  • 3
  • 28
  • 36
  • ... At first I thought this was not an answer, but it turns out to be one with the following addendum: if you go ahead and assign ID's to each node at creation time, you can then do: d3.select(this).attr('id'), and you now have a working reference to the DOM object. Thanks! – XML Apr 26 '12 at 20:57
  • ALSO note that what you really want to access is the ID of the node itself, not the node of the circle. So, insert the line `.attr("id", function(d, i) { return ("idlabel_" + i)})` into the function beginning with `d3.json("data/flare.json", function(json) {`, not into `node.append("circle.bubble")` – XML Apr 26 '12 at 21:09
  • That's a good point, though sometimes you do want to access the circle itself, if what you're doing is applying style changes to that element (which is what's going on in code where the example comes from. – Elijah Aug 01 '12 at 15:56