Skip to content

Instantly share code, notes, and snippets.

@fabiovalse
Last active December 19, 2016 15:27
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 fabiovalse/b9224bfd64ca96c47f8cdcb57b35b8e2 to your computer and use it in GitHub Desktop.
Save fabiovalse/b9224bfd64ca96c47f8cdcb57b35b8e2 to your computer and use it in GitHub Desktop.
Click to center & zoom

This example uses the zoom behaviour of D3.js (version 4) for enabling "click-to-zoom" interactions on SVG objects such as rectangles, circles and so on.

The D3 zoom.transform function has been used in order to create an animated transition every time an object is clicked.

In order to translate and scale the visualization, it is necessary to use the d3.zoomTransform function. In order to translate and scale the visualization, since the x, y and k properties are read-only, d3.zoomIdentity must be translated and scaled without accessing them directly.

By refreshing the visualization, random rectangles are generated.

svg = d3.select 'svg'
width = svg.node().getBoundingClientRect().width
height = svg.node().getBoundingClientRect().height
color = d3.schemeSet3
zoomable_layer = svg.append 'g'
zoom = d3.zoom()
.scaleExtent([1, 1000])
.on 'zoom', () ->
zoomable_layer
.attrs
transform: d3.event.transform
svg.call zoom
### Returns a transform for center a bounding box in the browser viewport
- W and H are the witdh and height of the window
- w and h are the witdh and height of the bounding box
- center cointains the coordinates of the bounding box center
- margin defines the margin of the bounding box once zoomed
###
to_bounding_box = (W, H, center, w, h, margin) ->
kw = (W - margin) / w
kh = (H - margin) / h
k = d3.min [kw, kh]
x = W/2 - center.x*k
y = H/2 - center.y*k
return d3.zoomIdentity
.translate x, y
.scale k
### Data
###
side = 5
n_columns = 5
n_rows = 2
fx = side*2 + 30 # the margin between circles on the columns
fy = side*2 + 30 # the margin between circles on the rows
data = [0..9].map (d,i) ->
return {
s1: side+Math.floor(Math.random()*20)
s2: side+Math.floor(Math.random()*20)
x: fx * (i%n_columns)
y: fy * (i%n_rows)
}
### Visualization
###
items = zoomable_layer.selectAll 'item'
.data data
en_items = items.enter().append 'rect'
.attrs
class: 'item'
all_items = en_items.merge(items)
all_items
.attrs
width: (d) -> d.s1
height: (d) -> d.s2
x: (d) -> d.x
y: (d) -> d.y
fill: (d,i) -> color[i]
.on 'click', (d,i) ->
center = {
x: d.x + d.s1/2
y: d.y + d.s2/2
}
transform = to_bounding_box(width, height, center, d.s1, d.s2, height/10)
svg.transition().duration(2000).call(zoom.transform, transform)
# Center vis on load
transform = to_bounding_box(width, height, {x: 170/2, y: 70/2}, 170, 70, 150)
svg.call(zoom.transform, transform)
body, html {
width: 100%;
height: 100%;
padding: 0;
margin: 0;
}
svg {
width: 100%;
height: 100%;
background: #F8F8F8;
}
.item {
stroke: #606060;
stroke-width: 1;
}
.item:hover {
stroke: #404040;
cursor: pointer;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://d3js.org/d3-selection-multi.v0.4.min.js"></script>
<script src="https://d3js.org/d3-scale-chromatic.v1.min.js"></script>
<link rel="stylesheet" href="index.css">
<title>Click to center & zoom</title>
</head>
<body>
<svg></svg>
<script src="index.js"></script>
</body>
</html>
// Generated by CoffeeScript 1.10.0
(function() {
var all_items, color, data, en_items, fx, fy, height, items, n_columns, n_rows, side, svg, to_bounding_box, transform, width, zoom, zoomable_layer;
svg = d3.select('svg');
width = svg.node().getBoundingClientRect().width;
height = svg.node().getBoundingClientRect().height;
color = d3.schemeSet3;
zoomable_layer = svg.append('g');
zoom = d3.zoom().scaleExtent([1, 1000]).on('zoom', function() {
return zoomable_layer.attrs({
transform: d3.event.transform
});
});
svg.call(zoom);
/* Return a transform for center a bounding box in the browser viewport
- w and h are the witdh and height of the container
- center cointains the coordinates of the bounding box center
- side_lengths is an array containing the length of the bounding box sides
- margin defines the margin of the bounding box once zoomed
*/
to_bounding_box = function(W, H, center, w, h, margin) {
var k, kh, kw, x, y;
kw = (W - margin) / w;
kh = (H - margin) / h;
k = d3.min([kw, kh]);
x = W / 2 - center.x * k;
y = H / 2 - center.y * k;
return d3.zoomIdentity.translate(x, y).scale(k);
};
/* Data
*/
side = 5;
n_columns = 5;
n_rows = 2;
fx = side * 2 + 30;
fy = side * 2 + 30;
data = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9].map(function(d, i) {
return {
s1: side + Math.floor(Math.random() * 20),
s2: side + Math.floor(Math.random() * 20),
x: fx * (i % n_columns),
y: fy * (i % n_rows)
};
});
/* Visualization
*/
items = zoomable_layer.selectAll('item').data(data);
en_items = items.enter().append('rect').attrs({
"class": 'item'
});
all_items = en_items.merge(items);
all_items.attrs({
width: function(d) {
return d.s1;
},
height: function(d) {
return d.s2;
},
x: function(d) {
return d.x;
},
y: function(d) {
return d.y;
},
fill: function(d, i) {
return color[i];
}
}).on('click', function(d, i) {
var center, transform;
center = {
x: d.x + d.s1 / 2,
y: d.y + d.s2 / 2
};
transform = to_bounding_box(width, height, center, d.s1, d.s2, height / 10);
return svg.transition().duration(2000).call(zoom.transform, transform);
});
transform = to_bounding_box(width, height, {
x: 170 / 2,
y: 70 / 2
}, 170, 70, 150);
svg.call(zoom.transform, transform);
}).call(this);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment