Reputation: 151
How can I create a function that returns a custom SVG graphic without appending it to anything? Can I create an empty selection and return that? What I currently have:
function makeGraphic(svgParent) {
return svgParent.append('circle').attrs({...});
}
This is what I want:
function makeGraphic() {
return d3.makeCircle?.attrs({...});
}
svgParent.append(makeGraphic());
Upvotes: 3
Views: 931
Reputation: 102174
It seems to me that this is the perfect situation for using a detached element. You could also use DocumentFragment, but a detached element is more idiomatic in a D3 code.
For creating the detached whatever
SVG element all you need is:
var detached = d3.create('svg:whatever');
According to the documentation,
Given the specified element name, returns a single-element selection containing a detached element of the given name in the current document.
This is very similar to Andrew's answer, because internally d3.create
uses document.createElement
. But pay attention, it uses document.createElement
, not document.createElementNS
, so the namespace is necessary here:
var detached = d3.create('svg:whatever');
//this is the namespace----ˆ
Then, after that, you can create the structure you want. For instance:
detached.selectAll(null)
.data(d3.range(5))
.enter()
.append("circle")
//etc...
Finally,get the node of the detached element and append it wherever you want, using append
:
svg.append(function(){ return detached.node();});
Here is a demo, the function makeGraphic
creates a detached element, and returns its node. Then, we just append the returned value to the SVG:
var svg = d3.select("body")
.append("svg")
.attr("width", 500)
.attr("height", 300);
function makeGraphic() {
var detached = d3.create('svg:g');
detached.selectAll(null)
.data(d3.range(10))
.enter()
.append("circle")
.attr("cx", function() {
return Math.random() * 500
})
.attr("cy", function() {
return Math.random() * 300
})
.attr("r", function() {
return Math.random() * 50
})
.attr("fill", function(_, i) {
return d3.schemeCategory10[i]
});
return detached.node();
};
svg.append(makeGraphic)
<script src="https://d3js.org/d3.v5.min.js"></script>
Upvotes: 2
Reputation: 38151
You could create a temporary svg node with svg namespace inside the function - without rendering the svg. Then you can treat the svg as a typical node while you manipulate it and return some sort of shape.
One thing to note is that .append()
accepts a tag name or a function, so I provide the function not its result below:
var svg = d3.select("body")
.append("svg")
.attr("width", 400)
.attr("height",200)
.attr("transform", "translate(200,100)");
var makeCircle = function() {
// create a temporary svg
let svg = document.createElementNS(d3.namespaces.svg, "svg")
// create a circle
let circle = d3.select(svg).append("circle")
.attr("r", 20)
.attr("fill", "steelblue");
// return a circle
return circle.node();
}
svg.append(makeCircle);
<script src="https://d3js.org/d3.v5.min.js"></script>
With that you could make a more complicated shape generator to return shapes for use with things like d3.append()
:
let svg = d3.select("body").append("svg")
.attr("width", 500)
.attr("height", 200);
var shapes = [{shape: "circle",y: 40, fill: "darkblue"},{shape:"square", y: 35},{y:40}]
svg.selectAll()
.data(shapes)
.enter()
.append(shapemaker);
function shapemaker(options = {}) {
let svg = document.createElementNS(d3.namespaces.svg, "svg")
var shape;
if (options.shape == "circle") {
shape = d3.select(svg).append("circle")
.attr("cx", options.x ? options.x : 50)
.attr("cy", options.y ? options.y : 50)
.attr("r", options.r ? options.r : 10)
.attr("fill", options.fill ? options.fill : "steelblue" )
}
else if (options.shape == "square") {
shape = d3.select(svg).append("rect")
.attr("x", options.x ? options.x : 100)
.attr("y", options.y ? options.y : 50)
.attr("width", options.width ? options.size : 10)
.attr("height", options.width ? options.size : 10)
.attr("fill", options.fill ? options.fill : "orange" )
}
else {
let x = options.x ? options.x : 150, y = options.y ? options.y : 50;
shape = d3.select(svg).append("path")
.attr("d", d3.symbol().type(d3.symbolStar).size(options.size ? options.size : 100))
.attr("transform","translate("+[x,y]+")")
.attr("fill", options.fill ? options.fill : "crimson")
}
return shape.node();
}
<script src="https://d3js.org/d3.v5.min.js"></script>
Upvotes: 2