JNPer JNPer - 1 month ago 9
Javascript Question

SVG & D3.js complex circle diagram

I have spent loads of time trying to replicate the attached diagram in SVG/D3.js. The closest I have come is by using the code attached. I have explored using the tree function but can only create one band of circles.

The issue with the attached code is it will be very hard to get it looking smooth and smart by manually typing which pixel coordinates each circle must sit.

For info, the dataset will do nothing more than colour these circles, so it can be entirely static in terms of shape. The code just needs to generate the shape, and I can then drive the colour based on the dataset.

image of what I am trying to create

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>D3 Test</title>
<script type="text/javascript" src="d3.v3.js"></script>
<style type="text/css">

</style>

</head>
<body>
<script type="text/javascript">

//Width and height
var w = 1000;
var h = 1000;
var dataset = [6,2,5,4,5,5,5,5,3,4,5,6];
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
var circles = svg.selectAll("circle")
.data(dataset)
.enter()
.append("circle");


circles.attr("cx", function (d, i) {
var Xaxis;
if (i === 0) { Xaxis = "500"; }
else if (i === 1) { Xaxis = "400"; }
else if (i === 2) { Xaxis = "420"; }
else if (i === 3) { Xaxis = "452.5"; }
else if (i === 4) { Xaxis = "485"; }
else if (i === 5) { Xaxis = "515"; }
else if (i === 6) { Xaxis = "547.5"; }
else if (i === 7) { Xaxis = "580"; }
else if (i === 8) { Xaxis = "600"; }
else if (i === 9) { Xaxis = "600"; }
else if (i === 10) { Xaxis = "650"; }
else if (i === 11) { Xaxis = "700"; }
else if (i === 12) { Xaxis = "750"; }
else if (i === 13) { Xaxis = "750"; }
else if (i === 14) { Xaxis = "750"; }
else if (i === 15) { Xaxis = "750"; }
else if (i === 16) { Xaxis = "750"; }
return Xaxis;
})
circles.attr("cy", function (d, i) {
var Yaxis;
if (i === 0) { Yaxis = "500"; }
else if (i === 1) { Yaxis = "500"; }
else if (i === 2) { Yaxis = "535"; }
else if (i === 3) { Yaxis = "560"; }
else if (i === 4) { Yaxis = "585"; }
else if (i === 5) { Yaxis = "585"; }
else if (i === 6) { Yaxis = "560"; }
else if (i === 7) { Yaxis = "535"; }
else if (i === 8) { Yaxis = "500"; }
else if (i === 9) { Yaxis = "600"; }
else if (i === 10) { Yaxis = "550"; }
else if (i === 11) { Yaxis = "500"; }
else if (i === 12) { Yaxis = "450"; }
else if (i === 13) { Yaxis = "600"; }
else if (i === 14) { Yaxis = "550"; }
else if (i === 15) { Yaxis = "500"; }
else if (i === 16) { Yaxis = "450"; }
return Yaxis;
})
.attr("r", function (d, i) {
var size;
if (i === 0) { size = "30"; }
else if (i > 0) { size = "20"; }
return size;
})
.attr("fill", function(d, i) {var returnColor;
if (d === 1) { returnColor = "green";}
else if (d === 2) { returnColor = "lightgreen";}
else if (d === 3) { returnColor = "gold"; }
else if (d === 4) { returnColor = "darkorange"; }
else if (d === 5) { returnColor = "red"; }
else if (d === 6) { returnColor = "lightgrey"; }
return returnColor;
});
</script>

</body>

</html>

Answer

You can use some trigonometry to positioning your circles. This is what you need:

To positioning the circle in the x axis:

x-centre + (distance * sin(angle))

And to positioning it in the y axis:

y-centre + (distance * cos(angle))

Where distance is the distance of the circles from the center and angle is the angle in radians. To calculate it, use:

rad = deg * Math.PI/180;

In the following snippet, I'm using groups to draw the layers of circles, and calculating the angle using the index of the circle in each group. I'm setting the number of circles using d3.range() and the number of layers in the same array that sets the distance from the centre. Check it:

var width = height = 500;
var svg = d3.select("body")
	.append("svg")
	.attr("width", width)
	.attr("height", height);
	
var color = d3.scaleOrdinal(d3.schemeCategory10)
  .domain(d3.range(16));
	
var data1 = d3.range(16);
var dataRadius = [70, 110, 150, 190, 230];

svg.append("circle").attr("cx", width/2)
	.attr("cy", height/2)
	.attr("r", 20)
	.attr("fill", "yellow");

var groups = svg.selectAll(".groups")
	.data(dataRadius)
	.enter()
	.append("g");

var circles = groups.selectAll(".circles")
	.data(data1)
	.enter()
	.append("circle");
	
circles.attr("cx", function(d,i){ var radius = this.parentNode.__data__; return width/2 + (radius*Math.sin(i*(360/(data1.length)*Math.PI/180)))})
	.attr("cy", function(d,i){var radius = this.parentNode.__data__; return height/2 + (radius*Math.cos(i*(360/(data1.length)*Math.PI/180)))})
	.attr("r", function(){return this.parentNode.__data__ == 230 ? 24 : 14})
	.attr("fill", function(d, i){ return i == 13 || i == 14 ? color(i-2) : "#ccc"});
<script src="https://d3js.org/d3.v4.min.js"></script>

Comments