I reckon that a good solution here would be creating a function to which you can pass the circles' attributes (cx
, cy
and r
), which would create the smileys based only on those values.
Creating the circles yourself
So, for instance, suppose that our circle's data has x
, y
, and r
as those attributes. We can create a function, here named makeSmileys
, that draws the circles and the path in the container group:
function makeSmileys(group, xPos, yPos, radius) {
//left eye
group.append("circle")
.attr("cx", xPos - radius / 3)
.attr("cy", yPos - radius / 3)
.attr("r", radius / 8)
.style("fill", "black");
//right eye
group.append("circle")
.attr("cx", xPos + radius / 3)
.attr("cy", yPos - radius / 3)
.attr("r", radius / 8)
.style("fill", "black");
arc.innerRadius(radius / 2)
.outerRadius(radius / 2.2);
//mouth
group.append("path")
.attr("d", arc)
.attr("transform", "translate(" + xPos + "," + yPos + ")");
}
As you can see, the position of the two eyes (circles) and the mouth (path) is based on the arguments only. You can tweak those positions the way you want.
For this function to work, we have to create the container groups and then call it on those respective selections:
circlesGroup.each(function(d) {
d3.select(this).call(makeSmileys, d.x, d.y, d.r)
})
Because I'm using selection.call
, the first argument (which is group
) is the selection itself. As an alternative, if you don't want to use selection.call
, just call the function as a normal JavaScript function, passing the container to it.
Here is a demo, with 10 randomly generated circles:
const svg = d3.select("svg");
const data = d3.range(10).map(function(d) {
return {
x: 50 + Math.random() * 500,
y: 50 + Math.random() * 300,
r: Math.random() * 50
}
});
const arc = d3.arc()
.startAngle(1 * (Math.PI / 2))
.endAngle(3 * (Math.PI / 2));
const circlesGroup = svg.selectAll(null)
.data(data)
.enter()
.append("g");
circlesGroup.each(function(d) {
d3.select(this).append("circle")
.attr("cx", d => d.x)
.attr("cy", d => d.y)
.attr("r", d => d.r)
.style("fill", "yellow")
.style("stroke", "black")
})
circlesGroup.each(function(d) {
d3.select(this).call(makeSmileys, d.x, d.y, d.r)
})
function makeSmileys(group, xPos, yPos, radius) {
group.append("circle")
.attr("cx", xPos - radius / 3)
.attr("cy", yPos - radius / 3)
.attr("r", radius / 8)
.style("fill", "black");
group.append("circle")
.attr("cx", xPos + radius / 3)
.attr("cy", yPos - radius / 3)
.attr("r", radius / 8)
.style("fill", "black");
arc.innerRadius(radius / 2)
.outerRadius(radius / 2.2);
group.append("path")
.attr("d", arc)
.attr("transform", "translate(" + xPos + "," + yPos + ")");
}
<script src="https://d3js.org/d3.v5.min.js"></script>
<svg width="600" height="400"></svg>
Using pre-existing circles
If you have an existing SVG (as you made clear in the edited question), you can select all circles with a selector...
const circles = svg.selectAll("circle");
...then get their attributes and finally call the function:
circles.each(function() {
const x = +d3.select(this).attr("cx");
const y = +d3.select(this).attr("cy");
const r = +d3.select(this).attr("r");
makeSmileys(x, y, r)
});
Mind the unary plus here, because the getters return strings for those attributes.
Here is the demo:
const svg = d3.select("svg");
const arc = d3.arc()
.startAngle(1 * (Math.PI / 2))
.endAngle(3 * (Math.PI / 2));
const circles = svg.selectAll("circle");
circles.each(function() {
const x = +d3.select(this).attr("cx");
const y = +d3.select(this).attr("cy");
const r = +d3.select(this).attr("r");
makeSmileys(x, y, r)
})
function makeSmileys(xPos, yPos, radius) {
svg.append("circle")
.attr("cx", xPos - radius / 3)
.attr("cy", yPos - radius / 3)
.attr("r", radius / 8)
.style("fill", "black");
svg.append("circle")
.attr("cx", xPos + radius / 3)
.attr("cy", yPos - radius / 3)
.attr("r", radius / 8)
.style("fill", "black");
arc.innerRadius(radius / 2)
.outerRadius(radius / 2.2);
svg.append("path")
.attr("d", arc)
.attr("transform", "translate(" + xPos + "," + yPos + ")");
}
<script src="https://d3js.org/d3.v5.min.js"></script>
<svg width="500" height="500" id="mySvg">
<circle class="mainCircle" cx=25 cy=25 r=25 fill="yellow"></circle>
<circle class="mainCircle" cx=125 cy=65 r=50 fill="yellow"></circle>
<circle class="mainCircle" cx=200 cy=12 r=10 fill="yellow"></circle>
<circle class="mainCircle" cx=210 cy=300 r=90 fill="yellow"></circle>
<circle class="mainCircle" cx=320 cy=25 r=5 fill="yellow"></circle>
<circle class="mainCircle" cx=400 cy=120 r=50 fill="yellow"></circle>
<circle class="mainCircle" cx=410 cy=230 r=25 fill="yellow"></circle>
</svg>