I have SVG nodes that I defined as <g>
elements that contain one <circle>
and one <text>
inside them.
Here's an examples HTML:
<g class="nodes">
<g>
<circle r="63"></circle>
<text class="true">Croatia</text>
</g>
(...)
I am animating them in d3js (v6) as to achieve a circular packing effect, using forces. All is working well, I just am not at all able to define the initial coordinates for my elements. The elements starting position is top right, and I would like them to be in the center of my SVG area when the animation starts.
I have tried giving my node <g>
elements "x, y" attributes, as well as transform: translate
them before the animation starts, to no avail.
I also tried initializing each object "x" and "y" values, before turning them into nodes, as so:
arrayOfCountries.forEach((country) => {
country["x"] = 300;
country["y"] = 700;
});
var simulation = d3
.forceSimulation()
//add nodes
.nodes(arrayOfCountries);
I have created an animations with:
simulation.on("tick", tickActions);
and animated it using:
function tickActions() {
//update g transform:
node.attr("transform", function (d) {
return "translate(" + [d.x, d.y] + ")";
});
}
Any idea how I could make sure the nodes start animating from center outwards?
Here's an outline of my code (I'm using Vue JS 3):
renderCountries() {
// #graph3 is my main SVG
var svg = d3.select("#graph3");
// Clear the previous map, if any
svg.selectAll("*").remove();
// create somewhere to put the force directed graph
let width = +svg.attr("width");
let height = +svg.attr("height");
let arrayOfCountries = this.euCountryList.values;
arrayOfCountries.forEach((country) => {
country["x"] = 300;
country["y"] = 700;
});
var simulation = d3
.forceSimulation()
//add nodes
.nodes(arrayOfCountries);
// add forces
// we're going to add a charge to each node
// also going to add a centering force
simulation
.force("charge_force", d3.forceManyBody().strength(-200))
.force("center_force", d3.forceCenter(width / 2, height / 2))
.force(
"x",
d3
.forceX()
.x(function (d) {
let lon = parseFloat(d[2].replace(",", ".").replace(" ", ""));
// shift 20 units so as to avoid negative numbers
let displayLon = (lon + 20) * 22;
return displayLon;
})
.strength(5)
)
.force(
"y",
d3
.forceY()
.y(function (d) {
let lat = parseFloat(d[1].replace(",", ".").replace(" ", ""));
let displayLat = height - lat * 10;
// console.log("LAT: " + displayLat);
return displayLat;
})
.strength(5)
)
.force(
"collide",
d3
.forceCollide()
.strength(0.01)
.radius(function (d) {
if (d[14]) {
let radius = parseFloat(
d[14].replace(",", ".").replace(" ", "")
);
return radius;
} else {
return 50;
}
})
.iterations(35)
); // Force that avoids circle overlapping;
// add tick instructions
simulation.on("tick", tickActions);
(...)
// draw circles
var node = svg
.append("g")
.attr("class", "nodes")
.selectAll("circle")
.data(arrayOfCountries)
.enter()
.append("g");
var circles = node
.append("circle")
.attr("r", function (d) {
if (d[14]) {
let radius = parseFloat(d[14].replace(",", ".").replace(" ", ""));
// Bail if radius is Not A Number; possibly a presedential election in a monarchy, eg, Spain.
if (isNaN(radius)) {
return 0;
}
if (radius > 35) {
return radius / 1.1;
} else {
// define a minimum radius
return 35;
}
// No data for abstention in country. Hide country in map.
} else {
return 0;
}
})
.attr("fill", "black")
.attr("filter", "url(#blurMe)")
.attr("ref", function (d) {
return d[0];
})
// Access country's caption using Vue template Refs
.attr("id", function (d) {
return d[0] + "-euparliament";
});
// eslint-disable-next-line no-unused-vars
var labels = node
.append("text")
.text(function (d) {
if (d[14] && d[14] != "nodata" && d[14] != "—") {
return d[3];
}
})
.attr("class", function (d) {
if (d[14]) {
let abstentionism = parseFloat(
d[14].replace(",", ".").replace(" ", "")
);
if (abstentionism) {
if (abstentionism < 35) {
return "small-country";
// return short name
}
}
}
return true;
})
.attr("text-anchor", "middle")
.attr("dominant-baseline", "middle")
.attr("pointer-events", "none")
// .attr("x", function (d) {
// return -(d.abstentionism / 2);
// })
.attr("y", 0)
.attr("fill", "red");
function tickActions() {
//update g transform:
node.attr("transform", function (d) {
return "translate(" + [d.x, d.y] + ")";
});
}
}