Reputation: 1137
I know what I want to accomplish but I don't know how to go about it.
Background: I have an interactive report, using D3, that uses circles to provide a visual reference to application usage by user location on top of a rendered map of the US by zip code. In places, these circles overlap, obscuring those underneath and preventing those circles underneath from receiving events, like mouseover for example. This creates a stack of circles.
Objective: I want to be able to click on a circle to push it to the bottom of the stack, though not beneath any background object that makes up the map. Or, as in this sample, not behind the .bgCircle.
Here is some sample code I put together in JSFiddle to illustrate the issue.
The CSS:
.bgCircle {
fill: blue;
opacity: .25
}
.popCircle {
fill: green;
opacity: 0.5;
stroke: black;
stroke-width: 1px;
}
svg {
border: 1px solid black;
}
The HTML
<svg width="500" height="500">
<g>
<circle class="bgCircle" cx="250" cy="250" r="250" />
<circle id="A" class="popCircle" cx="200" cy="200" r="100" />
<circle id="B" class="popCircle" cx="300" cy="200" r="100" />
<circle id="C" class="popCircle" cx="300" cy="300" r="100" />
<circle id="D" class="popCircle" cx="200" cy="300" r="100" />
</g>
</svg>
The JavaScript
d3.selectAll('.popCircle')
.on('click', function(d) {
var text = 'force circle ' + d3.select(this).attr('id') + ' to bottom of the popCircle stack but above the bgCircle';
alert(text);
});
Sample Project
https://jsfiddle.net/MissAmberClark/ssghj37v/
Upvotes: 3
Views: 549
Reputation: 102194
This is a solution for D3 v3.x, using vanilla JavaScript:
d3.selectAll("circle")
.on("click", function() {
var firstChild = this.parentNode.firstChild;
this.parentNode.insertBefore(this, firstChild);
})
Actually, the hint for doing this in D3 v3 is in D3 v4 API itself! It says:
selection.lower(): Re-inserts each selected element, in order, as the first child of its parent. Equivalent to:
selection.each(function() { this.parentNode.insertBefore(this, this.parentNode.firstChild); });
Here is the demo (I'm using @Andrew's SVG here):
d3.selectAll("circle")
.on("click", function() {
var firstChild = this.parentNode.firstChild;
this.parentNode.insertBefore(this, firstChild);
})
#A {
fill: orange;
}
#B {
fill: steelblue;
}
#C {
fill: pink;
}
#D {
fill: lawngreen;
}
<script src="https://d3js.org/d3.v3.min.js"></script>
<svg width="500" height="500">
<g>
<circle class="bgCircle" cx="250" cy="250" r="250" />
</g>
<g>
<circle id="A" class="popCircle" cx="200" cy="200" r="100" />
<circle id="B" class="popCircle" cx="300" cy="200" r="100" />
<circle id="C" class="popCircle" cx="300" cy="300" r="100" />
<circle id="D" class="popCircle" cx="200" cy="300" r="100" />
</g>
</svg>
Upvotes: 1
Reputation: 160
You should isolate the background circle in a separate group. This makes things easier.
Svg element are drawn in sequence so the first element will be behind everyone else. From your question it was unclear if you wanted the circle to be drawn in the back or put it in the last spot in the childeNode array. I added both solutions.
d3.selectAll('.popCircle')
.on('click', function(d) {
var parent = this.parentNode;
parent.removeChild(this);
//Insert behind
parent.insertBefore(this, parent.firstChild);
//Insert in front
//parent.appendChild(this);
});
.bgCircle {
fill: blue;
opacity: .25
}
.popCircle {
fill: green;
opacity: 0.5;
stroke: black;
stroke-width: 1px;
}
svg {
border: 1px solid black;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<svg width="500" height="500">
<g>
<circle class="bgCircle" cx="250" cy="250" r="250" />
</g>
<g>
<circle id="A" class="popCircle" cx="200" cy="200" r="100" />
<circle id="B" class="popCircle" cx="300" cy="200" r="100" />
<circle id="C" class="popCircle" cx="300" cy="300" r="100" />
<circle id="D" class="popCircle" cx="200" cy="300" r="100" />
</g>
</svg>
Upvotes: 1
Reputation: 38171
You can use selection.lower()
or selection.raise()
in d3v4 to move things up or down in the DOM (within the parent element). selection.lower()
will place an element at the bottom of its parent, while selection.raise()
will place an element at the top of its parent. These methods will therefore change the layering of svg elements as the ordering of the DOM sets the ordering of SVG elements.
So, in order to do this easily, the background elements should be in a separate <g>
so that they remain unaffected. Then, just raise or lower elements on click as needed (only the topmost element will trigger with each click event in the snippet):
d3.selectAll("circle")
.on("click",function() {
d3.select(this).lower();
})
#A { fill: orange; }
#B { fill: steelblue; }
#C { fill: pink; }
#D { fill: lawngreen; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.5.0/d3.min.js"></script>
<svg width="500" height="500">
<g>
<circle class="bgCircle" cx="250" cy="250" r="250" />
</g>
<g>
<circle id="A" class="popCircle" cx="200" cy="200" r="100" />
<circle id="B" class="popCircle" cx="300" cy="200" r="100" />
<circle id="C" class="popCircle" cx="300" cy="300" r="100" />
<circle id="D" class="popCircle" cx="200" cy="300" r="100" />
</g>
</svg>
Upvotes: 3