Skip to content

Instantly share code, notes, and snippets.

@ZoeLeBlanc
Last active March 14, 2018 15:03
Show Gist options
  • Save ZoeLeBlanc/547c5a2e5e9bdbf09cc2e2dbde2b07ac to your computer and use it in GitHub Desktop.
Save ZoeLeBlanc/547c5a2e5e9bdbf09cc2e2dbde2b07ac 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

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

forked from smithant'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() {
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;
console.log(kw, kh, W, H, center, w, h , margin);
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);
svg.transition().duration(2000).call(zoom.transform, transform);
});
transform = to_bounding_box(width, height, {
x: 170 / 2,
y: 70 / 2
}, 170, 70, 150);
}).call(this);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment