Last active
August 29, 2015 14:09
-
-
Save ptgolden/02a4d14a19e4999913cb to your computer and use it in GitHub Desktop.
Drag drop lines
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<meta charset="utf-8"> | |
<style> | |
html, body { margin: 0; padding: 0; } | |
#message { height: 3em; width: 800px; text-align: center; padding: 1em 0; box-sizing: border-box;} | |
</style> | |
<body> | |
<script src="http://d3js.org/d3.v3.min.js"></script> | |
<div id="message"></div> | |
<script> | |
var height = 300 | |
, width = 800 | |
, padding = 25 | |
, sensitiveZone = 100 | |
var svg = d3.select('body').append('svg') | |
.attr('height', height + 100) | |
.attr('width', width) | |
var y = d3.scale.linear() | |
.domain([0, 1]) | |
.range([height - padding, 0]) | |
// First, I'll set a number of variables that will be changed/monitored by | |
// different event callbacks. | |
// The line that will extend from one axis to another | |
var line = null; | |
// The group of sensitive rectangles that will be "sticky" to receive the line | |
var sensitiveRects = null; | |
// The current sensitive rectangle that is being hovered over | |
var enteredEl = null; | |
// The source and destination axes of the line. | |
var source = null, end = null; | |
// A function that returns an absolute X coordinate, relative to the SVG canvas, | |
// that is in the middle of the axis path. Takes the <g> axis element as an | |
// argument. | |
function xCoordFromAxis(axisGroup) { | |
var boundingRect = d3.select(axisGroup) | |
.select('.domain')[0][0] | |
.getBoundingClientRect() | |
return boundingRect.left + (boundingRect.width / 2); | |
} | |
// The behavior that will be called on the sensitive rectangles that are over | |
// the axes. Monitors drag events and handles drawing the line. | |
var drag = d3.behavior.drag() | |
.on('dragstart', function () { | |
// Save the reference to the sensitive rectangle which is the source of the | |
// drag event. | |
var that = this; | |
// Reset the message at the top. | |
document.querySelector('#message').innerHTML = ''; | |
// Save the source axis and measure. | |
// (y.invert(n) is the opposite of y(n)- it takes a pixel measure and returns | |
// the scaled value) | |
source = { axis: this, measure: y.invert(d3.mouse(this)[1]) } | |
// Add event listeners for all the sensitive rectangles that are *not* the | |
// source of this event. Store that element when one has been entered | |
// (mouseover), and unset that reference when it has been exited (mouseout) | |
sensitiveRects = d3.selectAll('rect') | |
.filter(function () { return that !== this }) | |
.on('mouseover', function () { enteredEl = this }) | |
.on('mouseout', function () { enteredEl = null }); | |
}) | |
.on('drag', function (e) { | |
// Get the position of the mouse relative to the SVG element | |
var mousePos = d3.mouse(svg[0][0]) | |
, mouseX = mousePos[0] | |
, mouseY = mousePos[1] | |
// If we have not drawn the line yet, create a new line element and make its | |
// origin x coordinate the middle of its source axis and its origin y | |
// coordinate the y position of the mouse. | |
if (!line) { | |
line = svg.insert('line', ':first-child') | |
.attr('x1', xCoordFromAxis(source.axis.parentNode)) | |
.attr('y1', mouseY) | |
} | |
// If the mouse is over a sensitive rectangle (as set by the mouseover and | |
// mouseout listeners set in dragstart), set the terminating point of the | |
// line at the middle of the the destination axis and y coordinate of the | |
// mouse. Otherwise, set it at the x and y position of the mouse. | |
if (enteredEl) { | |
dest = { axis: enteredEl, measure: y.invert(d3.mouse(enteredEl)[1]) } | |
line.attr('x2', xCoordFromAxis(dest.axis.parentNode)) | |
line.attr('stroke', 'red'); | |
} else { | |
line.attr('x2', mouseX); | |
line.attr('stroke', 'black'); | |
} | |
line.attr('y2', mouseY) | |
}) | |
.on('dragend', function (e) { | |
// If the drag ended while over a sensitive rectangle, report the source | |
// and desitination mesaures. | |
if (enteredEl) { | |
document.querySelector('#message').innerHTML = ( | |
d3.select(source.axis).datum().label + | |
'(' + source.measure.toFixed(2) + ') to ' + | |
d3.select(dest.axis).datum().label + | |
'(' + dest.measure.toFixed(2) + ').' | |
) | |
var newline = svg.insert('g', ':first-child'); | |
var lineCoords = { | |
x1: xCoordFromAxis(source.axis.parentNode), | |
y1: y(source.measure) + 20, | |
x2: xCoordFromAxis(dest.axis.parentNode), | |
y2: y(dest.measure) + 20, | |
} | |
newline.append('line') | |
.attr(lineCoords) | |
.attr('stroke', 'blue') | |
newline.append('line') | |
.attr(lineCoords) | |
.attr('stroke', 'blue') | |
.attr('stroke-width', 10) | |
.attr('opacity', 0) | |
newline.on('dblclick', function (e) { d3.select(this).remove(); return false; }) | |
} | |
// Remove the listeners on the sensitive zones, remove the line. | |
sensitiveRects.on('mouseover', null).on('mouseout', null); | |
line.remove(); | |
// Reset all the variables. | |
line = null; | |
sensitiveRects = null; | |
enteredEl = null; | |
source = null; | |
dest = null; | |
}) | |
var axis = d3.svg.axis() | |
.scale(y) | |
.orient('left') | |
.ticks(5) | |
// Draw 4 different axes | |
for (var i = 0; i < 4; i++) { | |
var axisG = svg.append('g') | |
.datum({ label: 'Axis #' + (i + 1) }) | |
.call(axis) | |
.attr('transform', 'translate(' + (50 + i * 200) + ',20)') | |
axisG.append('text') | |
.text(function (d) { return d.label }) | |
.attr('y', height) | |
.attr('text-anchor', 'middle') | |
// Draw a rectangle over the axis, and call the drag behavior on it. | |
var target = axisG | |
.append('rect') | |
.attr('height', function () { | |
return d3.select(this.parentNode).select('.domain')[0][0].getBoundingClientRect().height | |
}) | |
.attr('width', sensitiveZone) | |
.attr('transform', 'translate(-' + (sensitiveZone / 2 + 4) + ',0)') | |
.attr('fill', 'grey') | |
.style('opacity', '0') | |
.call(drag) | |
} | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment