block by nitaku e130ae36d2131abc56ae

Zoom

Full Screen

This example shows a way to create a zoomable SVG using d3.js‘s zoom behavior.

Orange shapes are added to a group that can be zoomed with the usual mouse or touch gestures. Purple shapes behave a little differently, keeping the same size regardless of the zoom (this is often referred to as semantic zoom). Finally, blue shapes are placed outside the zoomable group, and thus are never scaled or translated.

index.js

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

  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() {
    zoomable_layer.attr({
      transform: "translate(" + (zoom.translate()) + ")scale(" + (zoom.scale()) + ")"
    });
    return zoomable_layer.selectAll('.semantic_zoom').attr({
      transform: "scale(" + (1 / zoom.scale()) + ")"
    });
  });

  svg.call(zoom);

  zoomable_layer.append('circle').attr({
    r: 60,
    fill: 'orange'
  });

  zoomable_layer.append('rect').attr({
    x: -60,
    y: -160,
    width: 120,
    height: 60,
    fill: 'orange'
  });

  zoomable_layer.append('rect').attr({
    x: -60,
    y: 100,
    width: 120,
    height: 60,
    fill: 'orange'
  });

  zoomable_layer.append('g').attr({
    transform: "translate(" + 120 + "," + 0 + ")"
  }).append('circle').attr({
    "class": 'semantic_zoom',
    r: 20,
    fill: 'purple'
  });

  zoomable_layer.append('g').attr({
    transform: "translate(" + (-120) + "," + 0 + ")"
  }).append('rect').attr({
    "class": 'semantic_zoom',
    x: -20,
    y: -20,
    width: 40,
    height: 40,
    fill: 'purple'
  });

  svg.append('circle').attr({
    r: 40,
    cx: 400,
    cy: 180,
    fill: 'teal'
  });

  svg.append('rect').attr({
    x: -444,
    y: -220,
    width: 80,
    height: 80,
    fill: 'teal'
  });

}).call(this);

index.html

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <meta name="description" content="Zoom" />
        <title>Zoom</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

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 -  a value of 1 represent the initial zoom
  .on 'zoom', () ->
    # GEOMETRIC ZOOM
    # whenever the user zooms,
    # modify translation and scale of the zoomable layer accordingly
    zoomable_layer
      .attr
        transform: "translate(#{zoom.translate()})scale(#{zoom.scale()})"
    
    # SEMANTIC ZOOM
    # scale back all objects that have to be semantically zoomed
    zoomable_layer.selectAll('.semantic_zoom')
      .attr
        transform: "scale(#{1/zoom.scale()})"
    

# bind the zoom behavior to the main SVG (this is needed to have pan work on empty space - a group would pan only when dragging child elements)
svg.call(zoom)


# add geometrically zoomable content directly to zoomable layer
zoomable_layer.append('circle')
  .attr
    r: 60
    fill: 'orange'
    
zoomable_layer.append('rect')
  .attr
    x: -60
    y: -160
    width: 120
    height: 60
    fill: 'orange'
    
zoomable_layer.append('rect')
  .attr
    x: -60
    y: 100
    width: 120
    height: 60
    fill: 'orange'
    
# add each semantically zoomable object to a container group that defines the center of the object with a transition
# then add the group to the zoomable layer - this allows for scaling the object keeping its center fixed
# also specify a class for semantically zoomable objects to retrieve them in the zoom behavior callback above
zoomable_layer.append('g')
    .attr(
      transform: "translate(#{120},#{0})"
    )
  .append('circle')
    .attr
      class: 'semantic_zoom'
      r: 20
      fill: 'purple'
      
zoomable_layer.append('g')
    .attr(
      transform: "translate(#{-120},#{0})"
    )
  .append('rect')
    .attr
      class: 'semantic_zoom'
      x: -20
      y: -20
      width: 40
      height: 40
      fill: 'purple'

# add fixed content to main svg
svg.append('circle')
  .attr
    r: 40
    cx: 400
    cy: 180
    fill: 'teal'
    
svg.append('rect')
  .attr
    x: -444
    y: -220
    width: 80
    height: 80
    fill: 'teal'

index.css

svg {
  background: white;
}