block by nitaku 1ea254444b4a3fb73dd3

Archimedean spiral with a given angular length

Full Screen

-

index.js

(function() {
  var HEIGHT, SAMPLES, THETA_SLIDER_Y, WIDTH, a, brushed, handle, redraw, slider, spiral, svg, theta_max_brush, theta_max_scale;

  WIDTH = 960;

  HEIGHT = 500;

  svg = d3.select('body').append('svg').attr({
    width: WIDTH,
    height: HEIGHT
  }).append('g').attr({
    transform: "translate(" + (WIDTH / 2) + ",80)"
  });

  a = 0;

  SAMPLES = 80;

  svg.append('line').attr({
    "class": 'my_axis',
    x1: -WIDTH,
    x2: WIDTH
  });

  svg.append('line').attr({
    "class": 'my_axis',
    y1: -HEIGHT,
    y2: HEIGHT
  });

  spiral = svg.append('path').attr({
    "class": 'spiral'
  });

  theta_max_scale = d3.scale.linear().domain([0, 360 * 12]).range([-WIDTH / 2 + 50, WIDTH / 2 - 50]).clamp(true);

  theta_max_brush = d3.svg.brush().x(theta_max_scale).extent([0, 0]);

  THETA_SLIDER_Y = -50;

  svg.append('g').attr('class', 'x axis').attr('transform', "translate(0," + THETA_SLIDER_Y + ")").call(d3.svg.axis().scale(theta_max_scale).orient('bottom').tickFormat(function(d) {
    return d + '°';
  }).tickSize(0).tickPadding(12)).select('.domain').select(function() {
    return this.parentNode.appendChild(this.cloneNode(true));
  }).attr('class', 'halo');

  slider = svg.append('g').attr('class', 'slider').call(theta_max_brush);

  slider.selectAll('.extent,.resize').remove();

  slider.select('.background').attr('transform', "translate(0," + (THETA_SLIDER_Y - 11) + ")").attr('height', 22);

  handle = slider.append('circle').attr('class', 'handle').attr('transform', "translate(0," + THETA_SLIDER_Y + ")").attr('r', 9);

  slider.call(theta_max_brush.event).transition().duration(750).call(theta_max_brush.extent([2 * 360, 2 * 360])).call(theta_max_brush.event);

  brushed = function() {
    var value;
    
    value = theta_max_brush.extent()[0];
    if (d3.event.sourceEvent) {
      value = theta_max_scale.invert(d3.mouse(this)[0]);
      theta_max_brush.extent([value, value]);
    }
    handle.attr('cx', theta_max_scale(value));
    return redraw(2 * Math.PI * value / 360, 16);
  };

  theta_max_brush.on('brush', brushed);

  redraw = function(theta_max, dist) {
    var b, line_generator, number_of_points, points, radius;

    b = dist / (2 * Math.PI);
    radius = a + b * theta_max;
    number_of_points = Math.ceil(theta_max / (2 * Math.PI) * SAMPLES);
    points = d3.range(0, number_of_points).map(function(i) {
      var theta;

      theta = i * 2 * Math.PI / SAMPLES;
      return {
        theta: theta - Math.PI / 2 - theta_max,
        r: a + b * theta
      };
    });
    points.push({
      theta: -Math.PI / 2,
      r: radius
    });
    line_generator = d3.svg.line().x(function(d) {
      return d.r * Math.cos(d.theta);
    }).y(function(d) {
      return radius + d.r * Math.sin(d.theta);
    }).interpolate('cardinal');
    return spiral.datum(points).attr({
      d: line_generator
    });
  };

}).call(this);

index.html

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="description" content="Archimedean spiral with a given angular length" />
  <title>Archimedean spiral with a given angular length</title>
  <link rel="stylesheet" href="index.css">
  <script src="//d3js.org/d3.v3.min.js"></script>
</head>
<body>
  <script src="index.js"></script>
</body>
</html>

index.coffee

WIDTH = 960
HEIGHT = 500

svg = d3.select('body').append('svg')
  .attr
    width: WIDTH
    height: HEIGHT
  .append('g')
    .attr
      transform: "translate(#{WIDTH/2},80)"
      

# WARNING the following assumes a=0
a = 0

# angle samples per turn (not good for large spirals!)
SAMPLES = 80

# draw axes
svg.append('line')
  .attr
    class: 'my_axis'
    x1: -WIDTH
    x2: WIDTH
    
svg.append('line')
  .attr
    class: 'my_axis'
    y1: -HEIGHT
    y2: HEIGHT
    
spiral = svg.append('path')
  .attr
    class: 'spiral'
    
# draw the controls 
theta_max_scale = d3.scale.linear()
  .domain([0, 360 * 12])
  .range([-WIDTH/2+50, WIDTH/2-50])
  .clamp(true)

theta_max_brush = d3.svg.brush()
    .x(theta_max_scale)
    .extent([0, 0])
    
THETA_SLIDER_Y = -50

svg.append('g')
    .attr('class', 'x axis')
    .attr('transform', "translate(0,#{THETA_SLIDER_Y})")
    .call(d3.svg.axis()
      .scale(theta_max_scale)
      .orient('bottom')
      .tickFormat((d) -> d + '°')
      .tickSize(0)
      .tickPadding(12))
  .select('.domain')
  .select(() -> this.parentNode.appendChild(this.cloneNode(true)) )
    .attr('class', 'halo')

slider = svg.append('g')
    .attr('class', 'slider')
    .call(theta_max_brush)

slider.selectAll('.extent,.resize')
    .remove()

slider.select('.background')
    .attr('transform', "translate(0,#{THETA_SLIDER_Y-11})")
    .attr('height', 22)

handle = slider.append('circle')
    .attr('class', 'handle')
    .attr('transform', "translate(0,#{THETA_SLIDER_Y})")
    .attr('r', 9)
    
# initial animation
slider
    .call(theta_max_brush.event)
  .transition()
    .duration(750)
    .call(theta_max_brush.extent([2*360, 2*360]))
    .call(theta_max_brush.event)
    
brushed = () ->
  value = theta_max_brush.extent()[0]

  if d3.event.sourceEvent # not a programmatic event
    value = theta_max_scale.invert(d3.mouse(this)[0])
    theta_max_brush.extent([value, value])

  handle.attr('cx', theta_max_scale(value))
  
  # redraw the spiral
  redraw(2*Math.PI * value/360, 16)
  
theta_max_brush
  .on('brush', brushed)
    
redraw = (theta_max, dist) ->
    b = dist/(2*Math.PI)
    
    # compute the radius
    radius = a + b * theta_max

    number_of_points = Math.ceil( theta_max/(2*Math.PI) * SAMPLES )
    points = d3.range(0, number_of_points).map (i) ->
      theta = i*2*Math.PI/SAMPLES
      # PI/2 is subtracted from angles to have the spiral end at the top
      return {theta: theta-Math.PI/2-theta_max, r: a + b*theta}
      
    points.push {theta: -Math.PI/2, r: radius}
        
    # draw the spiral
    line_generator = d3.svg.line()
      .x((d) -> d.r * Math.cos(d.theta))
      .y((d) -> radius + d.r * Math.sin(d.theta)) # translate by radius to keep the final point in the same position
      .interpolate('cardinal')

    spiral
      .datum(points)
      .attr
        d: line_generator
    

index.css

svg {
  background-color: white;
}
.spiral {
  fill: none;
  stroke: black;
  stroke-width: 2px;
}
.my_axis {
  fill: none;
  stroke: lightgray;
  stroke-dasharray: 24 6 2 6;
  shape-rendering: crispEdges;
}

.axis {
  font: 10px sans-serif;
  -webkit-user-select: none;
  -moz-user-select: none;
  user-select: none;
}
.axis .domain {
  fill: none;
  stroke: #000;
  stroke-opacity: .3;
  stroke-width: 10px;
  stroke-linecap: round;
}
.axis .halo {
  fill: none;
  stroke: #ddd;
  stroke-width: 8px;
  stroke-linecap: round;
}
.slider .handle {
  fill: #fff;
  stroke: #000;
  stroke-opacity: .5;
  stroke-width: 1.25px;
  pointer-events: none;
}