block by steveharoz 14995297063c0bca267d262efd3f7bd4

animacy

Full Screen

index.html

<!DOCTYPE html>
<meta charset="utf-8">
<style>
body { background-color: black; color: white; font-family: sans-serif; }
span, div { margin: 1em; float: left; }
input { vertical-align: bottom;  }
svg { float: left; border: white 3px solid; border-radius: 4px; cursor: none; margin-top: 1em; }
#cursor { fill: #293; }
</style>

<body>
<div>
  <span>Show where they look: <input type="checkbox" id="showLook" checked></span> <br>
  <span>Perpendicular offset: <input type="range" id="offsetSlider" value="50" min="0" max="100"></span>  <br>
  <span>Absolute offset: <input type="range" id="absoluteOffsetSlider" value="0" min="0" max="100"></span>  <br>
  <span>Look perpendicular: <input type="checkbox" id="lookPerpendicular"></span> <br>
</div>
<svg></svg>
<script src="//d3js.org/d3.v4.min.js"></script>
<script>

var margin = 15;
var width = 600 - margin*2,
    height = 500 - margin*2;

var dartSpeed = 2;
var perpendicularOffset = 50;
var angleOffset = 0;
var mouseLocation = [0,0];

class Dart {
  constructor(index) {
    this.x = Math.random() * width;
    this.y = Math.random() * height;
    this.velocityAngle = Math.random() * 360;
    this.velocityX = dartSpeed * Math.cos(this.velocityAngle);
    this.velocityY = dartSpeed * Math.sin(this.velocityAngle);
    this.lookAngle = 0; // the angle where this dart is looking
    this.lookX = 0; // the position where this dart is looking
    this.lookY = 0; // the position where this dart is looking
    this.lookSign = index % 2 ? 1 : -1; // looking to the right or left
  }

  updateMove() {
    this.x += this.velocityX;
    this.y += this.velocityY;
    if (this.x < 0 || this.x > width) { this.velocityX *= -1; }
    if (this.x < 0) { this.x *= -1; }
    if (this.x > width) { this.x = 2*width - this.x; }
    if (this.y < 0 || this.y > height) { this.velocityY *= -1; }
    if (this.y < 0) { this.y *= -1; }
    if (this.y > height) { this.y = 2*height - this.y; }
    this.lookAtMe();
  }

  lookAtMe() {
    var absoluteOffset = +d3.select('#absoluteOffsetSlider').property('value');
    var angle = Math.atan2(mouseLocation[1]-absoluteOffset-this.y, mouseLocation[0]-absoluteOffset-this.x);
    var perpendicular = angle - Math.PI/2 * this.lookSign;
    this.lookX = mouseLocation[0] - absoluteOffset + perpendicularOffset * Math.cos(perpendicular);
    this.lookY = mouseLocation[1] - absoluteOffset + perpendicularOffset * Math.sin(perpendicular);
    this.lookAngle = Math.atan2(this.lookY-this.y, this.lookX-this.x) * 180/Math.PI + angleOffset;
  }
}

var darts = d3.range(12).map(i => new Dart(i));

var svg = d3.select("body").select("svg")
    .attr("width", width + margin*2)
    .attr("height", height + margin*2)
    .append("g")
      .attr("transform", 'translate(' + [margin, margin] + ')' )
      .attr("width", width)
      .attr("height", height);

var dartGlyphs = svg.selectAll('path')
  .data(darts)
  .enter()
  .append('path')
    .attr("d", " M 10 0 L -10 -10 L -5 0 L -10 10") // arrow shape
    .attr("fill", (d,i) => d3.schemeCategory20[i % 20])
    .attr("transform", d => 'translate(' + [d.x, d.y] + ')');

var cursor = svg.append('circle')
    .attr('id', 'cursor')
    .attr("r", 13);

var lookAtGlyphs = svg.selectAll('.lookAt')
  .data(darts)
  .enter()
  .append('circle')
    .attr('class', 'lookAt')
    .attr("r", 3)
    .attr("fill", (d,i) => d3.schemeCategory20[i % 20])
    .attr("transform", d => 'translate(' + [d.lookX, d.lookY] + ')');

var lookAtMean = svg.append('path').attr('class', 'lookAtMean')
  .attr("fill", 'white')
  .attr("d", d3.symbol().size(10).type(d3.symbolStar)());

d3.select(window).on("mousemove", () => { mouseLocation = d3.mouse(svg.node()); });

d3.timer(function() {
  // update settings
  angleOffset = d3.select('#lookPerpendicular').property('checked') ? 90 : 0;
  perpendicularOffset = +d3.select('#offsetSlider').property('value');
  // update darts
  for(i=0; i < darts.length; i++)
    darts[i].updateMove();
  // update glyphs
  cursor.attr("transform", 'translate(' + mouseLocation + ')');
  dartGlyphs
    .attr("fill", (d,i) => d3.select('#showLook').property('checked') ? d3.schemeCategory20[i % 20] : 'white' )
    .attr("transform", d => 
      'translate(' + [d.x, d.y] + ')' + 
      'rotate(' + d.lookAngle + ')');
  lookAtGlyphs
    .attr("opacity", +d3.select('#showLook').property('checked'))
    .attr("transform", d => 'translate(' + [d.lookX, d.lookY] + ')');
  lookAtMean
    .attr("opacity", +d3.select('#showLook').property('checked'))
    .attr("transform", 'translate(' + [
      darts.map(d => d.lookX).reduce((a, b) => a + b) / darts.length, 
      darts.map(d => d.lookY).reduce((a, b) => a + b) / darts.length] + ')');
});

</script>