Starting with D3 Transitions

Aim

Explore D3 transitions. Make an object (circle ball) follow a path.

Background

I have been on again off again with D3 for a while now. This year I have decided to be on again. This page contains some notes about paths and transitions. I decided to revisit transitions because of the terrible/incorrect execution in another set of notes.

Example

This is the remarkably boring transition.

index.html

<!DOCTYPE html>
<html>

  <head> 
    <meta charset='utf-8'/>
    <link rel="stylesheet" href="base.css">
    <script src="d3.min.js"></script>
  </head>

  <body>

    <div class='container'>
      <div class='chart'>
      </div>
    </div>

    <script src="activity.js"></script>

  </body>
</html>

base.css

.container {
  height: 300px;
  width: 300px;
  background-color: #fff;
}


.chart {
  font: 10px sans-serif; 
  color: black;
}

path {
  fill: none;
  stroke: none;
  stroke-width: 1px;
}

Activity.js

// Initial setup
var width = 150;
    height = 150;

var svg = d3.select(".chart")
    .append("svg")
    .attr("width", width)
    .attr("height", height)

// Add a primary circle
var centerCircle = svg.append("circle")
   .attr("cx", width/2)
   .attr("cy", height/2)
   .attr("r", 40)
   .attr("fill", "white")
   .attr("stroke", "black")

// Add two reference lines 
var block1 = svg.append("line")
   .attr("x1", width-1)
   .attr("y1", 0)
   .attr("x2", width-1)
   .attr("y2", height)
   .attr("stroke", "black")

var block2 = svg.append("line")
   .attr("x1", width)
   .attr("y1", height)
   .attr("x2", width/2)
   .attr("y2", height)
   .attr("stroke", "black")

// Setup the moving ball variables
var startx = 75;
    starty = 75;
    radius = 60;
    maxDegree = 270;
    speed = 2000;

// Create dataset
var points = [[startx, starty],
              [startx + radius, starty]];

for (var ba=0; ba<maxDegree; ba++) {
  var pos = circlePoint(startx, 
                        starty, 
                        radius, 
                        ba)
  points.push([pos.x, pos.y])
}

// Add additional points to break away from the 
// circular movement.
points.push([startx + radius + 4, 
             starty - radius], 
            [startx + radius + 4, 
             starty + radius + 4])

// Convert points to a path
var path = svg.append("path")
    .data([points])
    .attr("d", d3.line())


// RUN
transition();


function circlePoint(x, y, radius, degree) {
  /*
  Calculate points around a circle.

  This function is used to generate 
  points along an SVG "path".

  Note that the position of 0 degrees is 
  at center-right (3 position on a clock) 
  rather than center-top (12 position on 
  a clock).  

         270
        -----
      /-  |  -\
  180 -   |__ - Start (0 degrees) 
      \-     -/
        -----
         90 

  Args
  ----
    x: Integer
       - Specifies the center x position 
         of a circle in an SVG 

    y: Integer
       - Specifies the center y position 
         of a circle in an SVG 

    radius: Integer
       - Specifies the radius of the 
         circle 

    degree: Integer (0-359)
       - Specifies the angle from the 
         center of the circle to the 
         edge. Degree is converted to 
         radians before calculation.

  Example
  -------
    circlePont(150, 150, 50, 0)
    >> Object { x: 200, y: 150 }

    circlePoint(150, 150, 50, 45)
    >> Object { x: 185.35533905932738, 
                y: 185.35533905932738 }

    circlePoint(150, 150, 50, 180)
    >> Object { x: 100, y: 150 }

    circlePoint(150, 150, 50, 270)
    >> Object { x: 150, y: 100 }

  */
  var a = degree * Math.PI / 180;
  var pointX = x + radius * Math.cos(a)
  var pointY = y + radius * Math.sin(a)
  var output = {x: pointX, y: pointY};
  return output
}



function transition() {
  /*
   This function handles all transition events.

   A green circle is created with a radius of 10.
   The ball is then handled by another function 
   called repeat. 

   The repeat function is responsible for moving 
   the ball along a path. However, to make the 
   transition repeat you need to uncomment the line
   containing the ".on" attribute. 

   This function requires several variables to 
   exist.

   Variables
   ---------
     points: Array of arrays 
             - Each array contains xy coordinates

     speed:  Integer
             - E.g. 3000 (=3 seconds)

     path:   SVG element
             - Any path created from "points" data
  */
  var ball = svg.append("circle")
   .attr("r", 10)
   .attr("transform", "translate(" + points[0] + ")")
   .attr("fill", "green")
   .attr("stroke", "black");

  repeat();

  function repeat() {
    ball.attr("fill", "green")
    ball.transition()
      .duration(speed)
      .attr("fill", "blue")
      .attrTween("transform", translatePosition(path.node()))
      //.on("end", repeat); //UNCOMMENT TO REPEAT
  }
}



function translatePosition(path) {
  /*
    This function is called for .attrTween.
    It is used with the "transform" attribute.

    Args
    ----
      path: SVG path

    Notes
    -----
      This function uses the relative 
      position (t) along the length of a path (l) 
      which seems like a proxy for time. 

      Note that the relative position (t) ranges 
      from 0 to 1 and does not change when increasing 
      or decreasing the duration of a transition.

    Example
    -------
    Assuming object is at x: 75; y: 75.

      >>> First point
      path length (l):  530.7391357421875
      time (t):         9.958835199999968e-8
      position (p):     SVGPoint { 
                        x: 75.00005340576172, 
                        y: 75 }
      returns:          translate(75.00005340576172, 
                                75)

      >>> Last point
      path length (l):  530.7391357421875
      time (t):         1
      position (p):     SVGPoint { 
                        x: 139, 
                        y: 139 }
      returns:          translate(139,139)

  */
  var l = path.getTotalLength();

  // The following function [apparently] does 
  // not use d, i or a - these are just placeholders? 
  return function(d, i, a) {

    // t = relative position along a path
    return function(t) {
      var p = path.getPointAtLength(t * l);

      // Print to console
      // Uncomment for testing
    /*
      console.log("path length (l): ", l)
      console.log("time (t): ", t)
      console.log("position (p): ", p)
      console.log("translate(" + p.x + "," + p.y + ")")
      console.log("\n")
    */
      return "translate(" + p.x + "," + p.y + ")";
    };
  };
}

results matching ""

    No results matching ""