Having fun with Adam Pearce's cool new D3 tool swoopyDrag.js.
Last active
March 16, 2016 05:45
-
-
Save jmuyskens/e3257654c27f01f2a0f9 to your computer and use it in GitHub Desktop.
This file contains 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
license: mit | |
height: 600 | |
border: no |
This file contains 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
var margin = { | |
top: 10, | |
right: 10, | |
bottom: 10, | |
left: 10 | |
} | |
var width = 960 - margin.right - margin.left, | |
height = 600 - margin.top - margin.bottom; | |
var svg = d3 | |
.select('body') | |
.append('svg') | |
.attr('height', height + margin.top + margin.bottom) | |
.attr('width', width + margin.right + margin.left) | |
.append('g') | |
.translate([margin.left, margin.top]) | |
xScale = d3.scale.linear() | |
.domain([0, width]) | |
.range([0, width]) | |
yScale = d3.scale.linear() | |
.domain([0, height]) | |
.range([0, height]) | |
svg.append('marker') | |
.attr('id', 'arrow') | |
.attr('viewBox', '-10 -10 20 20') | |
.attr('markerWidth', 20) | |
.attr('markerHeight', 20) | |
.attr('orient', 'auto') | |
.append('path') | |
.attr('d', 'M-6.75,-6.75 L 0,0 L -6.75,6.75') | |
function randRange(minMax) { | |
return minMax[0] + Math.random() * (minMax[1] - minMax[0]) | |
} | |
function randCurve(xRange, yRange, startX, startY) { | |
var endPoint = (randRange(xRange) - startX) + "," + (randRange(yRange) - startY); | |
var randPair = (randRange(xRange) - startX) + "," + (randRange(yRange) - startY); | |
var randPair2 = (randRange(xRange) - startX) + "," + (randRange(yRange) - startY); | |
return "M 0,0 C " + randPair + ',' + randPair2 + ',' + endPoint; | |
} | |
function createRandomAnnotation() { | |
var xVal = randRange(xScale.domain()); | |
var yVal = randRange(yScale.domain()); | |
var path = randCurve(xScale.domain(), yScale.domain(), xVal, yVal); | |
console.log(xVal, yVal, path); | |
return { | |
"xVal": xVal, | |
"yVal": yVal, | |
"path": path, | |
"text": "", | |
"textOffset": [ | |
0, | |
0 | |
] | |
} | |
} | |
var swoopy = d3.swoopyDrag() | |
.x(function(d){ return xScale(d.xVal) }) | |
.y(function(d){ return yScale(d.yVal) }) | |
.draggable(true) | |
.annotations(d3.range(100).map(createRandomAnnotation)) | |
var swoopySel = svg.append('g').call(swoopy) | |
swoopySel.selectAll('path').attr('marker-end', 'url(#arrow)') |
This file contains 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
(function(root, factory) { | |
if (typeof module !== 'undefined' && module.exports) { | |
module.exports = factory(require('d3')); | |
} else if (typeof define === 'function' && define.amd) { | |
define(['d3'], factory); | |
} else { | |
root.d3 = factory(root.d3); | |
} | |
}(this, function(d3) { | |
d3.selection.prototype.translate = function(xy) { | |
return this.attr('transform', function(d,i) { | |
return 'translate('+[typeof xy == 'function' ? xy.call(this, d,i) : xy]+')'; | |
}); | |
}; | |
d3.transition.prototype.translate = function(xy) { | |
return this.attr('transform', function(d,i) { | |
return 'translate('+[typeof xy == 'function' ? xy.call(this, d,i) : xy]+')'; | |
}); | |
}; | |
d3.selection.prototype.tspans = function(lines, lh) { | |
return this.selectAll('tspan') | |
.data(lines) | |
.enter() | |
.append('tspan') | |
.text(function(d) { return d; }) | |
.attr('x', 0) | |
.attr('dy', function(d,i) { return i ? lh || 15 : 0; }); | |
}; | |
d3.selection.prototype.append = | |
d3.selection.enter.prototype.append = function(name) { | |
var n = d3_parse_attributes(name), s; | |
//console.log(name, n); | |
name = n.attr ? n.tag : name; | |
name = d3_selection_creator(name); | |
s = this.select(function() { | |
return this.appendChild(name.apply(this, arguments)); | |
}); | |
return n.attr ? s.attr(n.attr) : s; | |
}; | |
d3.selection.prototype.insert = | |
d3.selection.enter.prototype.insert = function(name, before) { | |
var n = d3_parse_attributes(name), s; | |
name = n.attr ? n.tag : name; | |
name = d3_selection_creator(name); | |
before = d3_selection_selector(before); | |
s = this.select(function() { | |
return this.insertBefore(name.apply(this, arguments), before.apply(this, arguments) || null); | |
}); | |
return n.attr ? s.attr(n.attr) : s; | |
}; | |
var d3_parse_attributes_regex = /([\.#])/g; | |
function d3_parse_attributes(name) { | |
if (typeof name === "string") { | |
var attr = {}, | |
parts = name.split(d3_parse_attributes_regex), p; | |
name = parts.shift(); | |
while ((p = parts.shift())) { | |
if (p == '.') attr['class'] = attr['class'] ? attr['class'] + ' ' + parts.shift() : parts.shift(); | |
else if (p == '#') attr.id = parts.shift(); | |
} | |
return attr.id || attr['class'] ? { tag: name, attr: attr } : name; | |
} | |
return name; | |
} | |
function d3_selection_creator(name) { | |
return typeof name === "function" ? name : (name = d3.ns.qualify(name)).local ? function() { | |
return this.ownerDocument.createElementNS(name.space, name.local); | |
} : function() { | |
return this.ownerDocument.createElementNS(this.namespaceURI, name); | |
}; | |
} | |
function d3_selection_selector(selector) { | |
return typeof selector === "function" ? selector : function() { | |
return this.querySelector(selector); | |
}; | |
} | |
d3.wordwrap = function(line, maxCharactersPerLine) { | |
var w = line.split(' '), | |
lines = [], | |
words = [], | |
maxChars = maxCharactersPerLine || 40, | |
l = 0; | |
w.forEach(function(d) { | |
if (l+d.length > maxChars) { | |
lines.push(words.join(' ')); | |
words.length = 0; | |
l = 0; | |
} | |
l += d.length; | |
words.push(d); | |
}); | |
if (words.length) { | |
lines.push(words.join(' ')); | |
} | |
return lines; | |
}; | |
d3.ascendingKey = function(key) { | |
return typeof key == 'function' ? function (a, b) { | |
return key(a) < key(b) ? -1 : key(a) > key(b) ? 1 : key(a) >= key(b) ? 0 : NaN; | |
} : function (a, b) { | |
return a[key] < b[key] ? -1 : a[key] > b[key] ? 1 : a[key] >= b[key] ? 0 : NaN; | |
}; | |
}; | |
d3.descendingKey = function(key) { | |
return typeof key == 'function' ? function (a, b) { | |
return key(b) < key(a) ? -1 : key(b) > key(a) ? 1 : key(b) >= key(a) ? 0 : NaN; | |
} : function (a, b) { | |
return b[key] < a[key] ? -1 : b[key] > a[key] ? 1 : b[key] >= a[key] ? 0 : NaN; | |
}; | |
}; | |
d3.f = function(){ | |
var functions = arguments; | |
//convert all string arguments into field accessors | |
var i = 0, l = functions.length; | |
while (i < l) { | |
if (typeof(functions[i]) === 'string' || typeof(functions[i]) === 'number'){ | |
functions[i] = (function(str){ return function(d){ return d[str] }; })(functions[i]) | |
} | |
i++; | |
} | |
//return composition of functions | |
return function(d) { | |
var i=0, l = functions.length; | |
while (i++ < l) d = functions[i-1].call(this, d); | |
return d; | |
}; | |
}; | |
// store d3.f as convenient unicode character function (alt-f on macs) | |
if (typeof window !== 'undefined' && !window.hasOwnProperty('ƒ')) window.ƒ = d3.f; | |
// this tweak allows setting a listener for multiple events, jquery style | |
var d3_selection_on = d3.selection.prototype.on; | |
d3.selection.prototype.on = function(type, listener, capture) { | |
if (typeof type == 'string' && type.indexOf(' ') > -1) { | |
type = type.split(' '); | |
for (var i = 0; i<type.length; i++) { | |
d3_selection_on.apply(this, [type[i], listener, capture]); | |
} | |
} else { | |
d3_selection_on.apply(this, [type, listener, capture]); | |
} | |
return this; | |
}; | |
// for everyone's sake, let's add prop as alias for property | |
d3.selection.prototype.prop = d3.selection.prototype.property; | |
return d3; | |
})); |
This file contains 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
<head> | |
<link rel="stylesheet" href="style.css"> | |
<meta http-equiv="content-type" content="text/html; charset=UTF8"> | |
</head> | |
<body> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.min.js" charset="utf-8"></script> | |
<script type="text/javascript" src="swoopy-drag.js"></script> | |
<script type="text/javascript" src="d3-jetpack.js"></script> | |
<script src="chart.js"></script> | |
</body> |
This file contains 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
path { | |
fill: none; | |
stroke: black; | |
} | |
circle { | |
stroke: cornflowerblue; | |
} | |
svg { | |
position: absolute; | |
left: 0; | |
top: 0; | |
} |
This file contains 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
d3.swoopyDrag = function(){ | |
var x = d3.scale.linear() | |
var y = d3.scale.linear() | |
var annotations = [] | |
var annotationSel | |
var draggable = false | |
var dispatch = d3.dispatch('drag') | |
var textDrag = d3.behavior.drag() | |
.on('drag', function(d){ | |
var x = d3.event.x | |
var y = d3.event.y | |
d.textOffset = [x, y].map(Math.round) | |
d3.select(this).call(translate, d.textOffset) | |
dispatch.drag() | |
}) | |
.origin(function(d){ return {x: d.textOffset[0], y: d.textOffset[1]} }) | |
var circleDrag = d3.behavior.drag() | |
.on('drag', function(d){ | |
var x = d3.event.x | |
var y = d3.event.y | |
d.pos = [x, y].map(Math.round) | |
var parentSel = d3.select(this.parentNode) | |
var path = '' | |
parentSel.selectAll('circle').each(function(d){ | |
path = path + '' + d.type + d.pos | |
}) | |
parentSel.select('path').attr('d', path).datum().path = path | |
d3.select(this).call(translate, d.pos) | |
dispatch.drag() | |
}) | |
.origin(function(d){ return {x: d.pos[0], y: d.pos[1]} }) | |
var rv = function(sel){ | |
annotationSel = sel.selectAll('g').data(annotations) | |
annotationSel.exit().remove() | |
annotationSel.enter().append('g') | |
annotationSel.call(translate, function(d){ return [x(d), y(d)] }) | |
var textSel = annotationSel.append('text') | |
.call(translate, ƒ('textOffset')) | |
.text(ƒ('text')) | |
annotationSel.append('path') | |
.attr('d', ƒ('path')) | |
if (!draggable) return | |
annotationSel.style('cursor', 'pointer') | |
textSel.call(textDrag) | |
annotationSel.selectAll('circle').data(function(d){ | |
var points = [] | |
var i = 1 | |
var type = 'M' | |
var commas = 0 | |
for (var j = 1; j < d.path.length; j++){ | |
var curChar = d.path[j] | |
if (curChar == ',') commas++ | |
if (curChar == 'L' || curChar == 'C' || commas == 2){ | |
points.push({pos: d.path.slice(i, j).split(','), type: type}) | |
type = curChar | |
i = j + 1 | |
commas = 0 | |
} | |
} | |
console.log(d.path.slice(i, j)) | |
points.push({pos: d.path.slice(i, j).split(','), type: type}) | |
return points | |
}).enter().append('circle') | |
.attr({r: 8, fill: 'rgba(0,0,0,0)', stroke: '#333', 'stroke-dasharray': '2 2'}) | |
.call(translate, ƒ('pos')) | |
.call(circleDrag) | |
dispatch.drag() | |
} | |
rv.annotations = function(_x){ | |
if (typeof(_x) == 'undefined') return annotations | |
annotations = _x | |
return rv | |
} | |
rv.x = function(_x){ | |
if (typeof(_x) == 'undefined') return x | |
x = _x | |
return rv | |
} | |
rv.y = function(_x){ | |
if (typeof(_x) == 'undefined') return y | |
y = _x | |
return rv | |
} | |
rv.draggable = function(_x){ | |
if (typeof(_x) == 'undefined') return draggable | |
draggable = _x | |
return rv | |
} | |
return d3.rebind(rv, dispatch, 'on') | |
//no jetpack dependency | |
function translate(sel, pos){ | |
sel.attr('transform', function(d){ | |
var posStr = typeof(pos) == 'function' ? pos(d) : pos | |
return 'translate(' + posStr + ')' | |
}) | |
} | |
function ƒ(str){ return function(d){ return d[str] } } | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment