Reputation: 79023
I'd like to create a javascript function which can take a generic D3 selection, and append duplicates of it to an SVG object.
Here's a minimum working example:
<!DOCTYPE html>
<meta charset="utf-8">
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
svg = d3.select("body").append("svg")
.attr("width", 300)
.attr("height", 300);
circle = svg.append("circle")
.attr("cx", 100)
.attr("cy", 100)
.attr("r", 20)
function clone_selection(x, i) {
for (j = 0; j < i; j++) {
// Pseudo code:
// svg.append(an exact copy of x, with all the attributes)
}
}
clone_selection(circle, 5);
</script>
Mike Bostock said that this was impossible here but that was a while back.
Does anyone have any new thoughts about how this might be achieved? Remember, inside the function clone_selection
we have no idea what svg element(s) is/are in x.
Upvotes: 12
Views: 13292
Reputation: 570
And 4 years and 4 month later... I made this D3 plugin. https://github.com/jkutianski/d3-clone
Upvotes: 3
Reputation: 79023
Thanks to @nrabinowitz for pointing me to <use>
elements.
Here's the MWE complete with working solution:
<!DOCTYPE html>
<meta charset="utf-8">
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
svg = d3.select("body").append("svg")
.attr("width", 300)
.attr("height", 300);
circle = svg.append("circle")
.attr("id", "circleToClone")
.attr("cx", 100)
.attr("cy", 100)
.attr("r", 20)
function clone_selection(object, i) {
for (j = 0; j < i; j++) {
// Here's the solution:
cloned_obj = svg.append("use")
.attr("xlink:href","#" + object.attr("id"));
}
}
clone_selection(circle, 5);
</script>
Upvotes: 5
Reputation: 227
Maybe a bit late to answer, but while I developed my own solution I found this question. This is how I create duplicates.
d3.select("#some_id")
.append("div")
.attr("class","some_other_id")
.each(function(d) {
for (var i = 1; i < number_duplicate; i++) {
d3.select("#some_other_id")
.append("button")
.attr("type","button")
.attr("class","btn-btn")
.attr("id","id_here")
.append("div")
.attr("class","label")
.text("text_here")
}
});
I create a div, do .each() on it and put a for loop into the each function. The number some_number will give me the intended amount of duplicates.
Variations will be possible, probably a second for would work etc. Maybe a poor man's version - I am not a pro. I would like to hear your feed back.
Upvotes: 0
Reputation: 10075
This function makes a deep copy of d3's selection and returns the selection of copied elements:
function cloneSelection(appendTo, toCopy, times) {
toCopy.each(function() {
for (var i = 0; i < times; i++) {
var clone = svg.node().appendChild(this.cloneNode(true));
d3.select(clone).attr("class", "clone");
}
});
return appendTo.selectAll('.clone');
}
See demo here.
This function works also if the selection toCopy contains multiple elements.
But be aware, that it copies everything, along with classes, ids and other attributes of all inner elements, which might break your code, if you're directly referencing inner elements somewhere else. So keep an eye on your selections. Having a parent, that distinguishes clone from the original, and mentioning it in the selection chain will keep you safe.
A reasonable thing to do (if you really need id so much) is to have id set only on the outer element of what you're copying, where you can easily change it by modifying the function: d3.select(clone).attr("class", "clone").attr("id", "clone-" + i)
.
Upvotes: 5
Reputation: 79023
Here's another possibility: do things the long way. This gets round the problem with using <use>
elements where you can't set the style
or transform
attributes separately.
I'm surprised the amazing d3js
library doesn't feature something like this natively, but here's my hack:
function clone_d3_selection(selection, i) {
// Assume the selection contains only one object, or just work
// on the first object. 'i' is an index to add to the id of the
// newly cloned DOM element.
var attr = selection.node().attributes;
var length = attr.length;
var node_name = selection.property("nodeName");
var parent = d3.select(selection.node().parentNode);
var cloned = parent.append(node_name)
.attr("id", selection.attr("id") + i);
for (var j = 0; j < length; j++) { // Iterate on attributes and skip on "id"
if (attr[j].nodeName == "id") continue;
cloned.attr(attr[j].name,attr[j].value);
}
return cloned;
}
Upvotes: 12