block by nitaku 9cba6c39cc335725f09f

Zoom + drag

Full Screen

This example combines the previous two about zooming and dragging. Care must be taken on avoiding the unwanted propagation of drag events from shapes to the whole SVG.

index.js

(function() {
  var drag, height, nodes, nodes_data, svg, width, zoom, zoomable_layer;

  nodes_data = [
    {
      x: 0,
      y: 0
    }, {
      x: 80,
      y: 80
    }, {
      x: -80,
      y: 80
    }, {
      x: -80,
      y: -80
    }, {
      x: 80,
      y: -80
    }
  ];

  svg = d3.select('svg');

  width = svg.node().getBoundingClientRect().width;

  height = svg.node().getBoundingClientRect().height;

  svg.attr({
    viewBox: "" + (-width / 2) + " " + (-height / 2) + " " + width + " " + height
  });

  zoomable_layer = svg.append('g');

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

  svg.call(zoom);

  drag = d3.behavior.drag().origin(function(d) {
    return d;
  });

  drag.on('dragstart', function() {
    return d3.event.sourceEvent.stopPropagation();
  });

  drag.on('drag', function(d) {
    d.x = d3.event.x;
    d.y = d3.event.y;
    return d3.select(this).attr({
      transform: "translate(" + d.x + "," + d.y + ")"
    });
  });

  nodes = zoomable_layer.selectAll('.node').data(nodes_data);

  nodes.enter().append('circle').call(drag).attr({
    "class": 'node',
    r: 20,
    fill: 'orange',
    transform: function(d) {
      return "translate(" + d.x + "," + d.y + ")";
    }
  });

}).call(this);

index.html

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <meta name="description" content="Zoom + drag" />
        <title>Zoom + drag</title>
        <link type="text/css" href="index.css" rel="stylesheet"/>
        <script src="//d3js.org/d3.v3.min.js"></script>
    </head>
    <body>
        <svg height="500" width="960"></svg>
        <script src="index.js"></script>
    </body>
</html>

index.coffee

# data
nodes_data = [
  {x:   0, y:   0},
  {x:  80, y:  80},
  {x: -80, y:  80},
  {x: -80, y: -80},
  {x:  80, y: -80}
]


svg = d3.select('svg')
width = svg.node().getBoundingClientRect().width
height = svg.node().getBoundingClientRect().height

# translate the viewBox to have (0,0) at the center of the vis
svg
  .attr
    viewBox: "#{-width/2} #{-height/2} #{width} #{height}"


# append a group for zoomable content
zoomable_layer = svg.append('g')

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

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


# define a drag behavior
drag = d3.behavior.drag()
  .origin((d) -> d)
  
drag.on 'dragstart', () ->
  # silence other listeners (disbale pan when dragging)
  # see https://github.com/mbostock/d3/wiki/Drag-Behavior
  d3.event.sourceEvent.stopPropagation()
  
drag.on 'drag', (d) ->
  # update the datum of the dragged node
  d.x = d3.event.x
  d.y = d3.event.y
  
  # translate the representation
  d3.select(this)
    .attr
      transform: "translate(#{d.x},#{d.y})"


# add draggable nodes
nodes = zoomable_layer.selectAll('.node')
    .data(nodes_data)

nodes.enter().append('circle')
  .call(drag) # enable dragging for nodes
  .attr
    class: 'node'
    r: 20
    fill: 'orange'
    transform: (d) -> "translate(#{d.x},#{d.y})"
    
    

index.css

svg {
  background: white;
}