Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

D3: Spiral plot

Can someone help me implementing a spiral chart similar to the one below using d3.js?

enter image description here

I've just got the basic spiral plot (a simple one) as of now but not been able to append bars to the plot based on the timeline as shown in the image. I'm trying out a few things (if you see the commented code).

Here's my fiddle, and my code:

      var width = 400,
      height = 430,
      axes = 12,
      tick_axis = 9,
      start = 0,
      end = 2.25;

  var theta = function(r) {
      return 2 * Math.PI * r;
  };

  var angle = d3.scale.linear()
      .domain([0, axes]).range([0, 360])

  var r = d3.min([width, height]) / 2 - 40;
  var r2 = r;

  var radius = d3.scale.linear()
      .domain([start, end])
      .range([0, r]);

  var svg = d3.select("#chart").append("svg")
      .attr("width", width)
      .attr("height", height)
      .append("g")
      .attr("transform", "translate(" + width / 2 + "," + (height / 2 + 8) + ")");

  var points = d3.range(start, end + 0.001, (end - start) / 1000);

  var spiral = d3.svg.line.radial()
      .interpolate("cardinal")
      .angle(theta)
      .radius(radius);

  var path = svg.selectAll(".spiral")
      .data([points])
      .enter().append("path")
      .attr("class", "spiral")
      .attr("d", spiral)


  var z = d3.scale.category20();

  var circles = svg.selectAll('.circle')
      .data(points);

  /*  circles.enter().append('circle')
              .attr('r', 5)
          .attr('transform', function(d) { return 'translate(' + d + ')'})
          .style('fill', function(d) { return z(d); });

      */

  var circle = svg.append("circle")
      .attr("r", 13)
      .attr("transform", "translate(" + points[0] + ")");

  var movingCircle = circle.transition().duration(4000)
      .attrTween('transform', translateAlongPath(path.node()))
      //            .attr('cx', function(d) { return radius(d) * Math.cos(theta(d))})
      //        .attr('cy', function(d) { return radius(d) * Math.sin(theta(d))})


  function translateAlongPath(path) {
      var l = path.getTotalLength();
      return function(d, i, a) {
          return function(t) {

              var p = path.getPointAtLength(t * l);
              //console.log(p)
              return "translate(" + p.x + "," + p.y + ")";
          };
      };
  }

  function pathXY(path) {
      var l = path.getTotalLength();
      var start = 0;

      /*  for(i=start; i<l; i++) {
          var point = path.getPointAtLength(i);
          svg.append('rect').transition().duration(400).attr('transform', 'translate(' + point.x +','+point.y+')')
          .attr('width', 10).attr('height', 30).style('fill', z);
        }*/
  }
  pathXY(path.node());

  /*var test = translateAlongPath(path.node())()();
  //console.log(test)
  var bars = svg.selectAll('.bar')
      .data(points).enter().append('rect').transition().duration(2000)
  //  .attrTween('transform', translateAlongPath(path.node()))
    .attr('class', 'bar')
    .attr('width', 10)
    .attr('height', 20)
    .style('fill', function(d) { return z(d)});
  */
  var rect = svg.append('rect').attr('width', 10).attr('height', 10);
  rect.transition().duration(3400)
      .attrTween('transform', translateAlongPath(path.node()));

It'd be great to have a few similar examples (i.e. spiral timeline plot).

Thanks.

like image 354
Shashank Avatar asked Jul 24 '26 04:07

Shashank


1 Answers

Glad you came back and updated your question, because this is an interesting one. Here's a running minimal implementation. I've commented it ok, so let me know if you have any questions...

<!DOCTYPE html>
<html>

<head>
  <script data-require="[email protected]" data-semver="4.0.0" src="https://d3js.org/d3.v4.js"></script>
</head>

<body>
  <div id="chart"></div>
  <script>
    var width = 500,
      height = 500,
      start = 0,
      end = 2.25,
      numSpirals = 4;

    var theta = function(r) {
      return numSpirals * Math.PI * r;
    };

    var r = d3.min([width, height]) / 2 - 40;

    var radius = d3.scaleLinear()
      .domain([start, end])
      .range([40, r]);

    var svg = d3.select("#chart").append("svg")
      .attr("width", width)
      .attr("height", height)
      .append("g")
      .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");

    // create the spiral, borrowed from http://bl.ocks.org/syntagmatic/3543186
    var points = d3.range(start, end + 0.001, (end - start) / 1000);

    var spiral = d3.radialLine()
      .curve(d3.curveCardinal)
      .angle(theta)
      .radius(radius);

    var path = svg.append("path")
      .datum(points)
      .attr("id", "spiral")
      .attr("d", spiral)
      .style("fill", "none")
      .style("stroke", "steelblue");

    // fudge some data, 2 years of data starting today
    var spiralLength = path.node().getTotalLength(),
        N = 730,
        barWidth = (spiralLength / N) - 1;
    var someData = [];
    for (var i = 0; i < N; i++) {
      var currentDate = new Date();
      currentDate.setDate(currentDate.getDate() + i);
      someData.push({
        date: currentDate,
        value: Math.random()
      });
    }
    
    // here's our time scale that'll run along the spiral
    var timeScale = d3.scaleTime()
      .domain(d3.extent(someData, function(d){
        return d.date;
      }))
      .range([0, spiralLength]);
    
    // yScale for the bar height
    var yScale = d3.scaleLinear()
      .domain([0, d3.max(someData, function(d){
        return d.value;
      })])
      .range([0, (r / numSpirals) - 30]);

    // append our rects
    svg.selectAll("rect")
      .data(someData)
      .enter()
      .append("rect")
      .attr("x", function(d,i){
        
        // placement calculations
        var linePer = timeScale(d.date),
            posOnLine = path.node().getPointAtLength(linePer),
            angleOnLine = path.node().getPointAtLength(linePer - barWidth);
      
        d.linePer = linePer; // % distance are on the spiral
        d.x = posOnLine.x; // x postion on the spiral
        d.y = posOnLine.y; // y position on the spiral
        
        d.a = (Math.atan2(angleOnLine.y, angleOnLine.x) * 180 / Math.PI) - 90; //angle at the spiral position

        return d.x;
      })
      .attr("y", function(d){
        return d.y;
      })
      .attr("width", function(d){
        return barWidth;
      })
      .attr("height", function(d){
        return yScale(d.value);
      })
      .style("fill", "steelblue")
      .style("stroke", "none")
      .attr("transform", function(d){
        return "rotate(" + d.a + "," + d.x  + "," + d.y + ")"; // rotate the bar
      });
    
    // add date labels
    var tF = d3.timeFormat("%b %Y"),
        firstInMonth = {};
    svg.selectAll("text")
      .data(someData)
      .enter()
      .append("text")
      .attr("dy", 10)
      .style("text-anchor", "start")
      .style("font", "10px arial")
      .append("textPath")
      // only add for the first of each month
      .filter(function(d){
        var sd = tF(d.date);
        if (!firstInMonth[sd]){
          firstInMonth[sd] = 1;
          return true;
        }
        return false;
      })
      .text(function(d){
        return tF(d.date);
      })
      // place text along spiral
      .attr("xlink:href", "#spiral")
      .style("fill", "grey")
      .attr("startOffset", function(d){
        return ((d.linePer / spiralLength) * 100) + "%";
      })

  </script>
</body>

</html>
like image 103
Mark Avatar answered Jul 26 '26 21:07

Mark



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!