Skip to content
Advertisement

D3.js v5 – appending lines about a circle from length of array

I want to make a visual that shows ordinal data (ratings). There are 12 rating dimensions, and each rating will have its own dedicated line appended to a circle. The polar orientation of the line designates a category (i.e. lines pointing to 1 o’clock = category 1, 2 o’clock = category 2, and so forth). The length of the line indicates the ratings value (short = bad, long = good). The result should resemble a snow flake or a sun burst.

The name is stored in a string. The ratings for each company are stored in an array. Here are two slices of my data variable:

  {'fmc':'fmc1', 'ratings':[10,10,10,10,10,10,10,10,10,10,10,10]},
  {'fmc':'fmc2', 'ratings':[8,10,10,5,10,10,10,10,10,7,10,5]},

I have the grid-system placement for the companies functioning, but there seems to be an issue with the way I’m aligning the lines about the circle. Relevant code:

var rotationDegree = d3.scalePoint().domain([0,12]).range([0, 2*Math.PI - Math.PI/6]);

fmcG.append('line')
    .data([10,10,10,10,10,10,10,10,10,10,10,10])
    .attr("x1", r)
    .attr("y1", r)
    .attr("x2", function(d,i) { return length(10) * Math.cos(rotationDegree(i) - Math.PI/2) + (width/2); })
    .attr("y2", function(d,i) { return length(10) * Math.sin(rotationDegree(i) - Math.PI/2) + (height/2); })
.style("stroke", function(d) { return "#003366" });

It would seem that I have the trig mapped out correctly, but in implementation I am proven wrong: the lines are not being appended about the circle like a snow flake / sun burst / clock.

Snippet:

var margins = {top:20, bottom:300, left:30, right:100};

var height = 600;
var width = 900;

var totalWidth = width+margins.left+margins.right;
var totalHeight = height+margins.top+margins.bottom;

var svg = d3.select('body')
.append('svg')
.attr('width', totalWidth)
.attr('height', totalHeight);

var graphGroup = svg.append('g')
.attr('transform', "translate("+margins.left+","+margins.top+")");

var data = [
  //{'fmc':'fmc1', 'ratings':[{'r1':10,'r2':10,'r3':10,'r4':10,'r5':10}]}
  {'fmc':'fmc1', 'ratings':[10,10,10,10,10,10,10,10,10,10,10,10]},
  {'fmc':'fmc2', 'ratings':[8,10,10,5,10,10,10,10,10,7,10,5]},
  {'fmc':'fmc3', 'ratings':[10,10,10,10,10,10,10,10,10,10,10,10]},
];

var r = 30;
var length = d3.scaleLinear().domain([0, 10]).range([0, 50]);
var rotationDegree = d3.scalePoint().domain([0,12]).range([0, 2*Math.PI - Math.PI/6]);

var columns = 5;
var spacing = 220;
var vSpacing = 250;

var fmcG = graphGroup.selectAll('.fmc')
  .data(data)
  .enter()
  .append('g')
  .attr('class', 'fmc')
  .attr('id', (d,i) => 'fmc' + i)
  .attr('transform', (d,k) => {
var horSpace = (k % columns) * spacing;
var vertSpace = ~~((k / columns)) * vSpacing;
return "translate("+horSpace+","+vertSpace+")";
  });


fmcG.append('circle')
.attr('cx',100)
.attr('cy',100)
.attr('r', r)
.style('fill','none')
.style('stroke','#003366');

fmcG.append('text')
.attr('x',100)
.attr('y',105)
.style('text-anchor','middle')
.text(function(d) {return d.fmc});

fmcG.append('line')
  //.data(function(d) {return d.ratings}) why doesnt it workk??????
.data([10,10,10,10,10,10,10,10,10,10,10,10])
.attr("x1", r)
.attr("y1", r)
.attr("x2", function(d,i) { return length(10) * Math.cos(rotationDegree(i) - Math.PI/2) + (width/2); })
.attr("y2", function(d,i) { return length(10) * Math.sin(rotationDegree(i) - Math.PI/2) + (height/2); })
.style("stroke", function(d) { return "#003366" });
<script src="https://d3js.org/d3.v5.min.js"></script>

Question

How can I take an 12-item array and append lines about the circle in 30 degree increments (360 divided by 12) while using the value of each item in the array to determine the line’s length?

Advertisement

Answer

The main issue is that, right now, you’re appending a single line. For appending as many lines as data points you have to set up a proper enter selection:

fmcG.selectAll(null)
  .data(function(d) {
    return d.ratings
  })
  .enter()
  .append('line')
  //etc...

And that, by the way, is the reason your data is not working (as you ask in your comment “why doesnt it workk??????”)

Other issues:

  1. A point scale needs to have a discrete domain, for instance d3.range(12)
  2. For whatever reason you’re moving the circles 100px right and down. I’m moving the lines by the same amount.

Here is the snippet with those changes:

var margins = {
  top: 20,
  bottom: 300,
  left: 30,
  right: 100
};

var height = 600;
var width = 900;

var totalWidth = width + margins.left + margins.right;
var totalHeight = height + margins.top + margins.bottom;

var svg = d3.select('body')
  .append('svg')
  .attr('width', totalWidth)
  .attr('height', totalHeight);

var graphGroup = svg.append('g')
  .attr('transform', "translate(" + margins.left + "," + margins.top + ")");

var data = [
  //{'fmc':'fmc1', 'ratings':[{'r1':10,'r2':10,'r3':10,'r4':10,'r5':10}]}
  {
    'fmc': 'fmc1',
    'ratings': [10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10]
  },
  {
    'fmc': 'fmc2',
    'ratings': [8, 10, 10, 5, 10, 10, 10, 10, 10, 7, 10, 5]
  },
  {
    'fmc': 'fmc3',
    'ratings': [10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10]
  },
];

var r = 30;
var length = d3.scaleLinear().domain([0, 10]).range([0, 50]);
var rotationDegree = d3.scalePoint().domain(d3.range(12)).range([0, 2 * Math.PI]);

var columns = 5;
var spacing = 220;
var vSpacing = 250;

var fmcG = graphGroup.selectAll('.fmc')
  .data(data)
  .enter()
  .append('g')
  .attr('class', 'fmc')
  .attr('id', (d, i) => 'fmc' + i)
  .attr('transform', (d, k) => {
    var horSpace = (k % columns) * spacing;
    var vertSpace = ~~((k / columns)) * vSpacing;
    return "translate(" + horSpace + "," + vertSpace + ")";
  });


fmcG.append('circle')
  .attr('cx', 100)
  .attr('cy', 100)
  .attr('r', r)
  .style('fill', 'none')
  .style('stroke', '#003366');

fmcG.append('text')
  .attr('x', 100)
  .attr('y', 105)
  .style('text-anchor', 'middle')
  .text(function(d) {
    return d.fmc
  });

fmcG.selectAll(null)
  .data(function(d) {
    return d.ratings
  })
  .enter()
  .append('line')
  .attr("x1", 100)
  .attr("y1", 100)
  .attr("x2", function(d, i) {
    return 100 + length(d) * Math.cos(rotationDegree(i));
  })
  .attr("y2", function(d, i) {
    return 100 + length(d) * Math.sin(rotationDegree(i));
  })
  .style("stroke", function(d) {
    return "#003366"
  });
<script src="https://d3js.org/d3.v5.min.js"></script>
Advertisement