|
// Generated by CoffeeScript 1.10.0 |
|
(function() { |
|
var data, fx, fy, get_main_object, height, lod, n_columns, n_rows, redraw, side, svg, to_bounding_box, transform, width, zoom, zoomable_layer; |
|
|
|
svg = d3.select('svg'); |
|
|
|
width = svg.node().getBoundingClientRect().width; |
|
|
|
height = svg.node().getBoundingClientRect().height; |
|
|
|
zoomable_layer = svg.append('g'); |
|
|
|
zoom = d3.zoom().scaleExtent([1, 1000]).on('zoom', function() { |
|
lod(d3.event.transform); |
|
return zoomable_layer.attrs({ |
|
transform: d3.event.transform |
|
}); |
|
}); |
|
|
|
svg.call(zoom); |
|
|
|
|
|
/* 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 { |
|
i: i, |
|
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 |
|
*/ |
|
|
|
redraw = function(x1, x2, y1, y2) { |
|
var all_items, en_items, in_viewport, items; |
|
in_viewport = function(d) { |
|
return d.x < x2 && d.x + d.s1 > x1 && d.y < y2 && d.y + d.s2 > y1; |
|
}; |
|
items = zoomable_layer.selectAll('.item').data(data.filter(function(d) { |
|
return in_viewport(d); |
|
})); |
|
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; |
|
} |
|
}).styles({ |
|
fill: '#f2f2f2' |
|
}).on('click', function(d) { |
|
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); |
|
}); |
|
return items.exit().remove(); |
|
}; |
|
|
|
|
|
/* |
|
*/ |
|
|
|
lod = function(transform) { |
|
var dx, dy, h_margin, v_margin, x, y; |
|
v_margin = 200 / transform.k; |
|
h_margin = 300 / transform.k; |
|
dx = width / transform.k; |
|
dy = height / transform.k; |
|
x = -transform.x / transform.k; |
|
y = -transform.y / transform.k; |
|
redraw(x, x + dx, y, y + dy); |
|
if (transform.k > 22) { |
|
get_main_object(x, x + dx, y, y + dy, h_margin, v_margin); |
|
} |
|
d3.select('.viewport').attrs({ |
|
x: x, |
|
y: y, |
|
width: dx, |
|
height: dy |
|
}); |
|
return d3.select('.m_viewport').attrs({ |
|
x: x + h_margin, |
|
y: y + v_margin, |
|
width: dx - h_margin * 2, |
|
height: dy - v_margin * 2 |
|
}); |
|
}; |
|
|
|
get_main_object = function(x1, x2, y1, y2, h_margin, v_margin) { |
|
var index, items, max; |
|
x1 += h_margin; |
|
x2 -= h_margin; |
|
y1 += v_margin; |
|
y2 -= v_margin; |
|
max = 0; |
|
index = null; |
|
items = zoomable_layer.selectAll('.item'); |
|
items.each(function(d, i) { |
|
var x_overlap, y_overlap; |
|
x_overlap = Math.max(0, Math.min(x2, d.x + d.s1) - Math.max(x1, d.x)); |
|
y_overlap = Math.max(0, Math.min(y2, d.y + d.s2) - Math.max(y1, d.y)); |
|
d.overlap = x_overlap * y_overlap; |
|
if (d.overlap > max) { |
|
max = d.overlap; |
|
return index = i; |
|
} |
|
}); |
|
return items.filter(function(d, i) { |
|
return i === index; |
|
}).styles({ |
|
fill: '#fb8072' |
|
}); |
|
}; |
|
|
|
|
|
/* 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 = 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); |
|
}; |
|
|
|
|
|
/* Init |
|
*/ |
|
|
|
redraw(); |
|
|
|
zoomable_layer.append('rect').attrs({ |
|
"class": 'viewport' |
|
}).styles({ |
|
fill: 'transparent', |
|
stroke: '#80b1d3', |
|
'stroke-width': 8, |
|
'vector-effect': 'non-scaling-stroke' |
|
}); |
|
|
|
zoomable_layer.append('rect').attrs({ |
|
"class": 'm_viewport' |
|
}).styles({ |
|
fill: 'transparent', |
|
stroke: '#fb8072', |
|
'stroke-width': 5, |
|
'vector-effect': 'non-scaling-stroke' |
|
}); |
|
|
|
transform = to_bounding_box(width, height, { |
|
x: 170 / 2, |
|
y: 70 / 2 |
|
}, 170, 70, 150); |
|
|
|
svg.call(zoom.transform, transform); |
|
|
|
}).call(this); |