Skip to content

Instantly share code, notes, and snippets.

@jmuyskens
Last active March 16, 2016 05:45
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 jmuyskens/e3257654c27f01f2a0f9 to your computer and use it in GitHub Desktop.
Save jmuyskens/e3257654c27f01f2a0f9 to your computer and use it in GitHub Desktop.
license: mit
height: 600
border: no
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)')
(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;
}));
<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>
path {
fill: none;
stroke: black;
}
circle {
stroke: cornflowerblue;
}
svg {
position: absolute;
left: 0;
top: 0;
}
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