d3.jsradial

D3 Linear radial clock


I'm trying to create a radial line chart with hours displayed in the middle of it. I'm getting the problem with rendering the text as on the picture below (red highlighted area).

enter image description here

  var xAxis = svg.selectAll('.radial').append("g");

      var xTick = xAxis
        // .selectAll("g")
        .selectAll(".radial")
        .data(x.ticks(23))
        .enter().append("g")
        .attr("text-anchor", "middle")
        .attr("transform", function(d) {
        return "rotate(" + ((x(d)) * 180 / Math.PI - 90) + ")translate(" + innerRadius + ",0)";
        });

      xTick.append("line")
        .attr("x2", -5)
        .attr("stroke", "#595D5C");

      xTick.append("text")
        .attr("transform", function(d) {
          var angle = x(d.key);
          return ((angle < Math.PI / 2) || (angle > (Math.PI * 3 / 2))) ? "rotate(90)translate(0,22)" : "rotate(-90)translate(0, -15)"; })
            .text(function(d) {
            return d;
      })
      .style("font-size", 10)
      .attr("color", "#595D5C")
      .attr("opacity", 1)

What am I doing incorrectly?


Solution

  • const margin = {top: 20, right: 10, bottom: 20, left: 10};
    
    const width = 650 - margin.left - margin.right,
      height = 650 - margin.top - margin.bottom;
    
    const innerRadius = 100,
        outerRadius = Math.min(width, height) / 2 - 6;
    
    const formatHour = d3.timeFormat("%I %p")
    
    const fullCircle = 2 * Math.PI * 23/24;
    
    const y = d3.scaleLinear()
        .range([innerRadius, outerRadius]);
    const x = d3.scaleLinear()
    
        x.range([0, fullCircle]);
    
    
        const line = d3.lineRadial()
      		.angle(function(d) { return x(d.key); })
      		.radius(function(d) { return y(d.value); })
          .curve(d3.curveCardinalClosed)
    
        data = [];
        for (i=0;i<24;i++){data.push({key:i, value:Math.round(Math.random()*5), class:0})};
    
        const svg = d3.select("body").append("svg").attr("width", width).attr("height", height);
        const gSelect = svg.selectAll('.radial').data(data);
    
        gSelect.exit()
        .classed('radial', false)
        .attr('opacity', 0.8)
        .transition()
        .attr('opacity', 0)
        .remove();
    
        // current.interrupt();
    
        var gEnter = gSelect.enter().append("g")
        // const g = svg
        // .append("g")
        .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")")
        .classed('radial', true);
    
        const exit = gSelect.exit().classed('radial', false);
        exit
        .attr('opacity', 0.8)
        .transition()
        .attr('opacity', 0)
        .remove();
    
        const mean = [];
    
        x.domain(d3.extent(data, function(d) { return d.key; }));
        y.domain(d3.extent(data, function(d) { return d.value; }));
    
        // var linePlot = gSelect.append("path")
        var linePlot = d3.selectAll('.radial').append("path")
          .datum(data)
          .attr("fill", "url(#gradientRainbow)")
          .attr("stroke", "#213946")
          .attr("stroke-width", 1)
          .attr('z-index', 200)
          .attr("d", line);
    
        var numColors = 10;
        var colorScale = d3.scaleLinear()
          .domain([0,(numColors-1)/2,numColors-1])
          .range(["#F5D801", "#74D877", "#2A4858"])
          .interpolate(d3.interpolateHcl);
    
        var gradient = d3.selectAll('.radial').append("defs").append("radialGradient")
          .attr("id", "gradientRainbow")
          .attr("gradientUnits", "userSpaceOnUse")
          .attr("cx", "0%")
          .attr("cy", "0%")
          .attr("r", "45%")
          .selectAll("stop")
          .data(d3.range(numColors))
          .enter().append("stop")
          .attr("offset", function(d,i) { return (i/(numColors-1)*50 + 40) + "%"; })
          .attr("stop-color", function(d) { return colorScale(d); });
    
        var yAxis = d3.selectAll('.radial').append("g")
        .attr("text-anchor", "middle");
    
        var yTick = yAxis
        .selectAll(".radial")
        // .selectAll("g")
        .data(y.ticks(5))
        .enter().append("g");
    
        yTick.append("circle")
          .attr("fill", "none")
          .attr("stroke", "#D8D8D8")
          .attr("opacity", 0.5)
          .attr("r", function(d) {return y(d)});
    
        //add avg
        yAxis.append("circle")
          .attr("fill", "none")
          .attr("stroke", "#2A41E5")
          .attr("stroke-width", 3)
          .attr("r", function() { return y(mean) });
    
        yAxis.append("circle")
          .attr("fill", "white")
          .attr("stroke", "black")
          .attr("opacity", 1)
          .attr("r", function() { return y(y.domain()[0])});
    
        yTick.append("text")
          .attr("y", function(d) { return -y(d); })
          .attr("dy", "0.35em")
          .text(function(d, i) {
            if (d === 0) {
            return ""
          }
          else {
            return d
          }
        });
    
    
          var xAxis = svg.selectAll('.radial').append("g");
    
          var xTick = xAxis
            // .selectAll("g")
            .selectAll(".radial")
            .data(x.ticks(24))
            .enter().append("g")
            .attr("text-anchor", "middle")
            .attr("transform", function(d) {
            return "rotate(" + ((x(d)) * 180 / Math.PI - 90) + ")translate(" + innerRadius + ",0)";
            });
    
          xTick.append("line")
            .attr("x2", -5)
            .attr("stroke", "#595D5C");
    
          xTick.append("text")
            .attr("transform", function(d) {
              var angle = x(d.key);
              return ((angle < Math.PI / 2) || (angle > (Math.PI * 3 / 2))) ? "rotate(90)translate(0,22)" : "rotate(-90)translate(0, -15)"; })
                .text(function(d) {
                return d;
          })
          .style("font-size", 10)
          .attr("color", "#595D5C")
          .attr("opacity", 1)
    <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.13.0/d3.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/d3-scale/1.0.7/d3-scale.min.js"></script>

    The range of x axis is defined as [0, fullCircle] and that is a problem - both 0 and a fullCircle represent the same point on the axis (0 degree = 360 degrees).

    Here is a very simple fix for that problem:

    const fullCircle = 2 * Math.PI * 23/24;
    

    And you can try running the code snippet to see it working.