There are numerous ways to achieve this. The first option below uses only rectangles, the second and third option use svg paths (all three options simplify positioning, modifiction of selection, changing shape from one to the other.)
The fourth option is an append where the elements vary between rect and circle.
Round some Rectangles
If your shapes are circle and square, perhaps the easiest is to use rectangles with rx, ry properties so that some rectangles appear like rectangles, and others like circles:
var data = [
{x: 100, y: 100, circle: true},
{x: 200, y: 100, circle: false}
]
var width = 20;
d3.select("body")
.append("svg")
.selectAll(null)
.data(data)
.enter()
.append("rect")
.attr("x",d=>d.x)
.attr("y",d=>d.y)
.attr("rx",d=>d.circle?width/2:0)
.attr("ry",d=>d.circle?width/2:0)
.attr("width", width)
.attr("height",width);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
Use D3.symbol
Instead of rects or circles, we could use paths, d3.symbol returns pathing data and has a circle and a rectangle, d3.symbol has a size method if we wish to alter the size (symbols are centered on their centering coordinate, like a circle):
var data = [
{x: 100, y: 100, circle: true},
{x: 200, y: 100, circle: false}
]
var width = 20;
d3.select("body")
.append("svg")
.selectAll(null)
.data(data)
.enter()
.append("path")
.attr("transform",d=>"translate("+[d.x,d.y]+")")
.attr("d", function(d) {
var symbol = d.circle ? d3.symbolCircle : d3.symbolSquare;
return d3.symbol().type(symbol).size(300)();
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
Make your own Paths
Instead of symbols we can just have a function that returns the path data for a circle or a rect, gives you more flexibility than d3.symbol, but loses some of the simplicity of it (such as easy rescaling of symbols):
var data = [
{x: 100, y: 100, circle: true},
{x: 200, y: 100, circle: false}
]
var width = 20;
d3.select("body")
.append("svg")
.selectAll(null)
.data(data)
.enter()
.append("path")
.attr("transform",d=>"translate("+[d.x,d.y]+")")
.attr("d", pather)
function pather(d) {
if(d.circle) {
return "M10,10m-10,0a10,10 0 1,0 20,0a 10,10 0 1,0 -20,0"
}
else {
return "M0,0L0,20L20,20L20,0Z";
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
Create rects and circles at the same time with one append
selection.append() can take a function - whatever element is returned by the function is appended to the DOM. To make our node, we'll create a detached SVG element and add a circle to that.
var data = [
{x: 100, y: 100, circle: true},
{x: 200, y: 100, circle: false}
]
var width = 20;
d3.select("body")
.append("svg")
.selectAll(null)
.data(data)
.enter()
.append(makeShape)
function makeShape(d) {
// create a temporary svg
let svg = document.createElementNS(d3.namespaces.svg, "svg")
// create an element:
if(d.circle) {
return d3.select(svg)
.append("circle")
.attr("cx",d.x)
.attr("cy",d.y)
.attr("r", 10)
.node() // return node, not selection.
}
else {
return d3.select(svg)
.append("rect")
.attr("x",d.x)
.attr("y",d.y)
.attr("width", 20)
.attr("height", 20)
.node();
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
But now you have a selection of circles and rectangles - much more awkward to deal with later.