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.
(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);
<!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>
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'
svg {
background: white;
}