Skip to content

Instantly share code, notes, and snippets.

@evanjmg
Last active February 19, 2017 17:20
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 evanjmg/d458114897dcf0316f786d90ed8147d8 to your computer and use it in GitHub Desktop.
Save evanjmg/d458114897dcf0316f786d90ed8147d8 to your computer and use it in GitHub Desktop.
var previousDraggedPosition = null,
selected = null;
// snap to grid is simply rounding to the nearest resolution of the square
function snapToGrid(p, r) {
return Math.round(p / r) * r;
}
// we'll use a resolution of 50 here
var cubeResolution = 50;
// randomly generate points, but make sure they snap to grid
var points = d3.range(10).map(() => {
return {
x: snapToGrid(Math.random() * 500, cubeResolution),
y: snapToGrid(Math.random() * 500, cubeResolution)
};
});
var itemContainer = view.selectAll("g").attr("class", "itemContainer")
// add group to view
.data(points).enter().append('g')
// and center the group in the middle
.attr("transform", () => 'translate(' + xScale(0) + ',' + yScale(0) + ')')
.append('g')
// make this entire group draggable - this is useful for adding text elements later
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
// add the square to each group
var item = itemContainer.append('rect').attr('class', 'table-graphic')
.attr('x', d => d.x)
.attr('y', d => d.y)
.attr('data-rotation', 0)
.attr('width', cubeResolution)
.attr('height', cubeResolution)
.attr('fill', 'blue')
.on('click', function() {
selected = this.parentNode;
});
function dragged(d) {
selected = this;
// update the position of the rect (square) and snap to grid
var el = d3.select(this).select('.table-graphic').attr("x", (d) => snapToGrid(d3.event.x, cubeResolution)).attr("y", () => snapToGrid(d3.event.y, cubeResolution))
// get center point and make sure rotation is correct on drag.
var center = getCenter(el.attr('x'), el.attr('y'), cubeResolution, cubeResolution);
el.attr('transform', () => {
return "rotate(" + el.attr('data-rotation') + "," + center.x + ',' + center.y + ")";
});
}
function dragended(d) {
d3.select(this).classed("dragging", false);
var newEl = d3.select(this).select('.table-graphic');
var newPt = {
x: newEl.attr('x'),
y: newEl.attr('y')
};
// save and update position for redraw
var pt = findAndUpdate(coorNum(previousDraggedPosition), coorNum(newPt));
if (pt) {
previousDraggedPosition = pt
};
}
function dragstarted(d) {
var el = d3.select(this);
// save previous drag point for collisions and redraws
savePreviousDragPoint(el);
// raise the z-index to the top and set class to dragging
el.raise().classed("dragging", true);
}
// helper to convert strings to integers
function coorNum(pt) {
return {
x: parseInt(pt.x, 10),
y: parseInt(pt.y, 10)
};
}
function savePreviousDragPoint(el) {
var elBox = el.nodes()[0].getBBox();
if (!el.nodes()[0].classList.contains('dragging')) {
previousDraggedPosition = {
x: elBox.x,
y: elBox.y
};
}
}
// helper for drag recentering
function getCenter(x, y, w, h) {
return {
x: parseInt(x, 10) + parseInt(w, 10) / 2,
y: parseInt(y, 10) + parseInt(h) / 2
}
};
// add slider instead of mousewheel zoom to improve user experience
// have it start at min 50% and max out at 5x the amount.
// you'll also have to add slider.attr('value', d3.event.scale) in the zoom method to update slider
var slider = d3.select("body").append("input")
.datum({})
.attr("type", "range")
.attr("value", 1)
.attr("min", zoom.scaleExtent()[0])
.attr("max", zoom.scaleExtent()[1])
.attr("step", (zoom.scaleExtent()[1] - zoom.scaleExtent()[0]) / 100)
.on("input", slided);
function slided(d) {
zoom.scaleTo(svg, d3.select(this).property("value"));
}
// disable zoom on mousewheel and double click
svg.call(zoom).on("wheel.zoom", null)
.on('dblclick.zoom', null);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment