Skip to content

Instantly share code, notes, and snippets.

@christianbriggs
Last active January 11, 2017 02:28
Show Gist options
  • Save christianbriggs/a2a3821d1ada49c07b036c0f2f6a0248 to your computer and use it in GitHub Desktop.
Save christianbriggs/a2a3821d1ada49c07b036c0f2f6a0248 to your computer and use it in GitHub Desktop.
Click to center & zoom
license: mit

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.

forked from fabiovalse's block: Click to center & zoom

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 ;
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