block by nitaku a734cc41152fa4b3e3bc

Interactive rota

Full Screen

Jean Deckers, Tabella ad inveniendum numerum Cycli solaris quocunque anno centesimo currente - a pretty old example of interactive visualization.

Questa tavola di Deckers è formata da dischi di carta concentrici rotanti attorno ad un perno comune, uno spago che attraversa la carta al centro. Essa era probabilmente allegata ad una lettera perduta di Deckers a Clavio, il cui contenuto verteva su aspetti della cronologia cristiana. La rota doveva infatti servire per dimostrare la validità delle posizioni cronologiche sostenute da Deckers.

Credits: Roma, Archivio storico della Pontificia Università Gregoriana, ms. APUG 530, c. 61r

index.js

(function() {
  var drag, dragging, highlight, rota_1, rota_2, svg, zoom, zoom_group;

  svg = d3.select('svg');

  zoom_group = svg.select('#zoom_group');

  zoom = d3.behavior.zoom().scaleExtent([1, 4]).on('zoom', function() {
    return zoom_group.attr({
      transform: "translate(" + (zoom.translate()) + ")scale(" + (zoom.scale()) + ")"
    });
  });

  svg.call(zoom);

  rota_1 = svg.select('#rota_1').datum({
    offset: 6,
    pinch_offset: null
  });

  rota_2 = svg.select('#rota_2').datum({
    offset: 20,
    pinch_offset: null
  });

  rota_1.transition().duration(1200).attr('transform', 'rotate(' + rota_1.datum().offset + ')');

  rota_2.transition().duration(2800).attr('transform', 'rotate(' + rota_2.datum().offset + ')');

  dragging = false;

  drag = d3.behavior.drag().on('dragstart', function() {
    d3.event.sourceEvent.stopPropagation();
    dragging = true;
    return d3.select(this).classed('highlighted', true);
  }).on('drag', function() {
    var angle, d, rota;

    rota = d3.select(this);
    d = rota.datum();
    if (d.pinch_offset == null) {
      d.pinch_offset = 180 / Math.PI * Math.atan2(d3.event.y, d3.event.x) + 180 - d.offset;
    }
    angle = 180 / Math.PI * Math.atan2(d3.event.y, d3.event.x) + 180;
    d.offset = angle - d.pinch_offset;
    return rota.attr('transform', 'rotate(' + d.offset + ')');
  }).on('dragend', function() {
    d3.select(this).datum().pinch_offset = null;
    dragging = false;
    return d3.select(this).classed('highlighted', false);
  });

  highlight = function(rota) {
    return rota.on('mousemove', function() {
      if (!dragging) {
        return d3.select(this).classed('highlighted', true);
      }
    }).on('mouseout', function() {
      if (!dragging) {
        return d3.select(this).classed('highlighted', false);
      }
    });
  };

  rota_1.call(drag).call(highlight);

  rota_2.call(drag).call(highlight);

  d3.select(self.frameElement).style('height', '800px');

}).call(this);

index.html

<!DOCTYPE html>
<html>
	<head>
        <meta charset="utf-8">
        <meta name="description" content="Interactive rota"/>
        <title>Interactive rota</title>
		<link type="text/css" href="index.css" rel="stylesheet"/>
        <script src="//d3js.org/d3.v3.min.js"></script>
	</head>
	<body>
        <svg height="800" width="960">
          <defs>
            <pattern id="rota_img"
              patternUnits="userSpaceOnUse"
              x="-481"
              y="-330"
              width="800"
              height="800"
              viewBox="0 0 800 800">
              <image
                xlink:href="//wafi.iit.cnr.it/webvis/tmp/clavius/rota_1.jpg"
                x="80"
                y="0"
                height="800"
                width="800"
              />
            </pattern>
          </defs>
          <g id="zoom_group">
            <g transform="translate(481,330)">
              <rect
                fill="url(#rota_img)"
                x="-481"
                y="-330"
                width="800"
                height="800"
              />
              <g id="rota_1" class="rota">
                <circle
                  fill="url(#rota_img)"
                  cx="0"
                  cy="0"
                  r="142"
                />
                <circle
                  class="highlight"
                  cx="0"
                  cy="0"
                  r="142"
                />
              </g>
              <g id="rota_2" class="rota">
                <circle
                  fill="url(#rota_img)"
                  cx="0"
                  cy="0"
                  r="112"
                />
                <circle
                  class="highlight"
                  cx="0"
                  cy="0"
                  r="112"
                />
              </g>
              <circle
              fill="url(#rota_img)"
              cx="0"
              cy="0"
              r="34"
            />
            </g>
          </g>
        </svg>
        
        <script src="index.js"></script>
	</body>
</html>

index.coffee

svg = d3.select('svg')

zoom_group = svg.select('#zoom_group')

# define a zoom behavior
zoom = d3.behavior.zoom()
  .scaleExtent([1,4]) # min-max zoom
  .on 'zoom', () ->
    # GEOMETRIC ZOOM
    zoom_group
      .attr
        transform: "translate(#{zoom.translate()})scale(#{zoom.scale()})"

# bind the zoom behavior to the main SVG
svg.call(zoom)

rota_1 = svg.select('#rota_1')
  .datum({
    offset: 6,
    pinch_offset: null
  })
  
rota_2 = svg.select('#rota_2')
  .datum({
    offset: 20,
    pinch_offset: null
  })

rota_1
  .transition().duration(1200)
    .attr('transform', 'rotate('+rota_1.datum().offset+')')
    
rota_2
  .transition().duration(2800)
    .attr('transform', 'rotate('+rota_2.datum().offset+')')
    
# define a drag behavior to let the user rotate the arcs
dragging = false

drag = d3.behavior.drag()
  .on 'dragstart', () ->
      # silence other listeners (disable pan when dragging)
      # see https://github.com/mbostock/d3/wiki/Drag-Behavior
      d3.event.sourceEvent.stopPropagation()
      
      dragging = true
      
      # highlight
      d3.select(this).classed('highlighted', true)
  .on 'drag', () ->
      rota = d3.select(this)
      d = rota.datum()
      if not d.pinch_offset?
          d.pinch_offset = 180/Math.PI*Math.atan2(d3.event.y,d3.event.x)+180-d.offset
            
      # current angle in positive degrees
      angle = 180/Math.PI*Math.atan2(d3.event.y, d3.event.x)+180
      d.offset = angle-d.pinch_offset
      rota
        .attr('transform', 'rotate('+d.offset+')')
  .on 'dragend', () ->
      d3.select(this).datum().pinch_offset = null
      
      dragging = false
      
      # highlight
      d3.select(this).classed('highlighted', false)
      
highlight = (rota) ->
  rota
    .on 'mousemove', () ->
      # highlight
      if not dragging
        d3.select(this).classed('highlighted', true)
    .on 'mouseout', () ->
      # highlight
      if not dragging
        d3.select(this).classed('highlighted', false)
  
rota_1
  .call(drag)
  .call(highlight)
  
rota_2
  .call(drag)
  .call(highlight)
  
  
d3.select(self.frameElement).style('height', '800px')

index.css

svg {
  background: #111;
}
.rota {
  cursor: move;
}
.rota .highlight {
  display: none;
  fill: yellow;
  opacity: 0.06;
}
.rota.highlighted .highlight {
  display: inline;
}