Circular point scale

Viewed 214

I'm aware of the external d3-scale-radial.js library, but I'm only going for a one dimensional circular scale (not 2 dimensional like radial bar charts or line charts). So I'm hoping I don't need to include a whole additional library for what seems to be a fairly simple task. Here is a image version I drafted using illustrator:

enter image description here

You'll note that there is no "y" axis here. It's basically a point scale, but rendered as a circle. While this is simpler than multi-dimensional radial charts, precision and scalability are important to me, so I'm reluctant to brute force it by appending lines as tick marks then conjuring up some trigonometric voodoo to position the circles at the exact decimal values along the circumference of the circle -- which seems to be the only native d3 solution.

Question

Is there anything that native d3 can offer to simplify a simple radial point chart? Or should I throw in the towel and embrace the radial.js library for the time being?

1 Answers

No, there is no native D3 scale for this, but you don't need any scale: a simple trigonometric math will do.

For the x position multiply the radius for the cosine of your value, while for the y position you multiply the radius for the sine of your value. Since your values go from 0 to 1, you position your circles with just this (where r is the radius):

.attr("cx", function(d) {
    return r * Math.cos(d * Math.PI * 2)
})
.attr("cy", function(d) {
    return r * Math.sin(d * Math.PI * 2)
})

However, since your axis starts at the top (that is, 12 o'clock), subtract it by Math.PI / 2:

.attr("cx", function(d) {
    return r * Math.cos(d * Math.PI * 2 - Math.PI / 2)
})
.attr("cy", function(d) {
    return r * Math.sin(d * Math.PI * 2 - Math.PI / 2)
})

Also, if your values don't go from 0 to 1 anymore use a basic linear scale for mapping them.

The most complicated thing is drawing the axis, but it's not that complicated.

Here is a demo using [0.1, 0.25, 0.7] as data:

const w = 300,
  h = 300,
  r = 100;

const data = [0.1, 0.25, 0.7];

const svg = d3.select("body")
  .append("svg")
  .attr("width", w)
  .attr("height", h);

const g = svg.append("g")
  .attr("transform", "translate(" + [w / 2, h / 2] + ")");

const axisLine = g.append("circle")
  .attr("r", r)
  .style("fill", "none")
  .style("stroke", "black");

const axisGroup = g.selectAll(null)
  .data(d3.range(0, 1.05, 0.05))
  .enter()
  .append("g")
  .attr("transform", function(d) {
    return "rotate(" + (-90 + (d * 360)) + ")";
  });

axisGroup.append("line")
  .attr("x1", r - 4)
  .attr("x2", r + 4)
  .style("stroke", "black");

const points = g.selectAll(null)
  .data(data)
  .enter()
  .append("circle")
  .style("fill", "dodgerblue")
  .attr("r", 8)
  .attr("cx", function(d) {
    return r * Math.cos(d * Math.PI * 2 - Math.PI / 2)
  })
  .attr("cy", function(d) {
    return r * Math.sin(d * Math.PI * 2 - Math.PI / 2)
  })
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>

Related