|
// Constants |
|
var GRID_SQUARE_SIZE = 50; |
|
|
|
var margin = { |
|
top: 20, |
|
right: 20, |
|
bottom: 20, |
|
left: 20 |
|
}; |
|
|
|
var numCols = 20; // from data.maxCols |
|
var numRanges = 20; // from data.maxRanges |
|
|
|
// Should this be limited |
|
var gridWidth = GRID_SQUARE_SIZE * (numCols + 1); |
|
var gridHeight = GRID_SQUARE_SIZE * (numRanges + 1); |
|
|
|
var width = gridWidth + margin.left + margin.right; |
|
var height = gridHeight + margin.top + margin.bottom; |
|
|
|
// Scales |
|
var x = d3.scale.linear() |
|
.domain([0, numCols]) |
|
.range([0, gridWidth]); |
|
|
|
var y = d3.scale.linear() |
|
.domain([0, numRanges]) |
|
.range([gridHeight, 0]); |
|
|
|
// Axes |
|
var xAxis = d3.svg.axis() |
|
.scale(x) |
|
.orient('top') |
|
.tickValues(d3.range(0, numCols + 1)) |
|
.tickFormat(d3.format("0d")) |
|
.tickSize(-gridHeight) |
|
|
|
var yAxis = d3.svg.axis() |
|
.scale(y) |
|
.orient('left') |
|
.tickValues(d3.range(0, numRanges + 1)) |
|
.tickFormat(d3.format("0d")) |
|
.tickSize(-gridWidth); |
|
|
|
// Zoom behavior (does this work for touch?) |
|
var zoom = d3.behavior.zoom() |
|
.x(x) |
|
.y(y) |
|
.scaleExtent([0.25, 2]) |
|
.on('zoom', zoomed); |
|
|
|
function zoomed() { |
|
// Redraw axes |
|
svg.select('.x.axis').call(xAxis); |
|
svg.select('.y.axis').call(yAxis); |
|
|
|
d3.selectAll(".x.axis .tick text") |
|
.call(shiftXAxisLabels); |
|
d3.selectAll(".y.axis .tick text") |
|
.call(shiftYAxisLabels); |
|
|
|
// Transform the repsG |
|
var t = zoom.translate(), |
|
tx = t[0], |
|
ty = t[1]; |
|
|
|
// This prevents user from moving the field completely off the graph. |
|
// TODO make it zoom dependent (e.g. if zoom far enough where whole thing |
|
// fits, then restrict pan so whole field is visible.) |
|
tx = Math.max(tx, -gridWidth * zoom.scale() * 0.99) |
|
tx = Math.min(tx, gridWidth * 0.99); |
|
ty = Math.max(ty, -gridHeight * zoom.scale() * 0.99) |
|
ty = Math.min(ty, gridHeight * 0.99); |
|
zoom.translate([tx,ty]); |
|
|
|
repsG.attr({ |
|
transform: "translate("+ zoom.translate() + ")scale(" + zoom.scale() +")" |
|
}) |
|
} |
|
|
|
// Top-level svg node |
|
var svg = d3.select('body') |
|
.append('svg') |
|
.attr({ |
|
width: width, |
|
height: height |
|
}); |
|
|
|
// Container group for axes and field grid |
|
var gridContainerG = svg.append('g') |
|
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')') |
|
.call(zoom); |
|
|
|
// Container group for the reps |
|
var repsContainerG = gridContainerG.append('g'); |
|
|
|
// The background rect |
|
repsContainerG.append('rect') |
|
.attr({ |
|
width: gridWidth, |
|
height: gridHeight |
|
}); |
|
|
|
// Draw axes and gridlines |
|
gridContainerG.append("g") |
|
.attr("class", "x axis") |
|
.call(xAxis); |
|
|
|
gridContainerG.append("g") |
|
.attr("class", "y axis") |
|
.call(yAxis); |
|
|
|
d3.selectAll(".axis .tick:first-child text").attr({display: "none"}) |
|
d3.selectAll(".x.axis .tick text").call(shiftXAxisLabels); |
|
d3.selectAll(".y.axis .tick text").call(shiftYAxisLabels); |
|
|
|
// The group holding the reps rects and big field rect |
|
var repsG = repsContainerG.append('g') |
|
.attr({ |
|
id: 'gridContainerG' |
|
}); |
|
|
|
// Rect demarcating the whole field |
|
repsG.append('rect') |
|
.attr({ |
|
class: 'field', |
|
width: gridWidth, |
|
height: gridHeight |
|
}); |
|
|
|
// UTILITY FUNCTIONS |
|
function addRep(coordinates, dimensions, info) { |
|
var repG = repsG.append('g').attr({ |
|
transform: 'translate(' + x(coordinates.x - 1) + ',' + y(coordinates.y + dimensions.r - 1) + ')' |
|
}); |
|
|
|
repG.append('rect') |
|
.attr({ |
|
class: 'field-rep', |
|
width: x(dimensions.c), |
|
height: x(dimensions.r) |
|
}) |
|
|
|
// TODO get it exactly in the middle and figure out how to handle |
|
// overflowing text |
|
repG.append('text') |
|
.attr('y', 10) |
|
.attr('x', 3) |
|
.text(info.set) |
|
} |
|
|
|
// TODO labels are still slightly off because of text-anchor property. |
|
function shiftXAxisLabels(sel) { |
|
var scale = d3.event ? d3.event.scale : 1; |
|
sel.attr('x', -GRID_SQUARE_SIZE / 2 * scale) |
|
} |
|
|
|
function shiftYAxisLabels(sel) { |
|
var scale = d3.event ? d3.event.scale : 1; |
|
sel.attr('y', GRID_SQUARE_SIZE / 2 * scale); |
|
} |
|
|
|
// Tests with loading data |
|
d3.json('rep-data.json', function(data) { |
|
data.rep.map(function(rep) { |
|
addRep({ |
|
x: rep.x, |
|
y: rep.y |
|
}, { |
|
c: rep.columnsWide, |
|
r: rep.rangesTall |
|
}, { |
|
set: rep.set |
|
}) |
|
}) |
|
}) |