Skip to content

Instantly share code, notes, and snippets.

@nitaku
Last active August 29, 2015 14:04
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nitaku/e130ae36d2131abc56ae to your computer and use it in GitHub Desktop.
Save nitaku/e130ae36d2131abc56ae to your computer and use it in GitHub Desktop.
Zoom

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.

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;
}
<!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="http://d3js.org/d3.v3.min.js"></script>
</head>
<body>
<svg height="500" width="960"></svg>
<script src="index.js"></script>
</body>
</html>
(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);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment