Last active
November 29, 2018 04:47
-
-
Save JoBerkner/5d50f674d030d8920e71f41081c6cf43 to your computer and use it in GitHub Desktop.
Hold + Tap for d3 v5 using touchpad (still with errors)
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
<!DOCTYPE html> | |
<meta charset="utf-8"> | |
<head> | |
<script src="http://d3js.org/d3.v5.js"></script> | |
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script> | |
</head> | |
<body> | |
<style> | |
#svg-div { | |
position: absolute; | |
width: 2400px; | |
height: 1200px; | |
} | |
.ref-circle{ | |
fill: none; | |
stroke: grey; | |
} | |
.drag-circle{ | |
fill: black; | |
stroke: none; | |
} | |
.target-circle{ | |
fill: red; | |
stroke: none; | |
} | |
.active { | |
stroke: #000; | |
stroke-width: 2px; | |
} | |
</style> | |
<div id="svg-div"></div> | |
<script> | |
///////////////////////////////////////////// | |
//THIS IS JUST SOME DRAWING SETTINGS UNIMPORTANT FOR THIS QUESTION, | |
//BUT IF YOU WANT TO RUN IT YOU CAN SEE SOME VISUAL FEEDBACK | |
///////////////////////////////////////////// | |
//----------------------------------------------------------------------- | |
//variables of svg | |
height = 1200, | |
width = 2400, | |
radiusDrag = 30, | |
radiusTargetList = [40,70,100], | |
refDistance = 800, | |
center = {x:1200, y:600}, | |
angle = 17.143; | |
//create parameters of circles | |
var refCircle = [{x: 1200, y: 600, r: 400}]; | |
//Choose current radius of target and delete its entry from the targetSize list | |
var radiusTarget = radiusTargetList[Math.floor(Math.random() * radiusTargetList.length)]; | |
radiusTargetList.splice(radiusTargetList.indexOf(radiusTarget),1); | |
//function to create the circle coordinates | |
function create_circles() { | |
var initDragCircle = []; | |
var initTargetCircle = []; | |
var angleSum = 0; | |
for (var i = 0; i < 21; i++){ | |
initDragCircle.push([{x: center.x + (refDistance/2) * Math.cos( (angleSum+angle) * Math.PI / 180 ), y: center.y + (refDistance/2) * Math.sin( (angleSum+angle) * Math.PI / 180 )}]); | |
initTargetCircle.push([{x: center.x + ((refDistance/2) + radiusTarget) * Math.cos( (angleSum+180+(angle/2)) * Math.PI / 180 ), y: center.y + ((refDistance/2) + radiusTarget) * Math.sin( (angleSum+180+(angle/2)) * Math.PI / 180 )}]); | |
angleSum += angle; | |
}; | |
var counter = 0; | |
var stepper = -11; | |
var dCircle = []; | |
var tCircle = []; | |
for (var i = 0; i < 21; i++){ | |
dCircle.push(initDragCircle[counter]); | |
tCircle.push(initTargetCircle[counter]); | |
if (stepper == -11) { | |
stepper = 10; | |
} else { | |
stepper = -11; | |
}; | |
if (counter == 10) { | |
counter = 20; | |
stepper = 10; | |
} else { | |
counter += stepper; | |
}; | |
}; | |
dragCircle = dCircle; | |
targetCircle = tCircle; | |
} | |
//----------------------------------------------------------------------- | |
//function to draw the experiment | |
function start_experiment() { | |
//create the circles | |
create_circles(); | |
//create svg | |
var svg = d3.select("#svg-div").append("svg") | |
.attr("width", width) | |
.attr("height", height); | |
//add circles to svg | |
svg.selectAll(".ref-circle") | |
.data(refCircle) | |
.enter() | |
.append('circle') | |
.attr("class", "ref-circle") | |
.attr("cx", function(d) { return d.x; }) | |
.attr("cy", function(d) { return d.y; }) | |
.attr("r", function(d) { return d.r; }); | |
svg.selectAll(".drag-circle") | |
.data(dragCircle[0]) | |
.enter() | |
.append('circle') | |
.attr("class", "drag-circle") | |
.attr("cx", function(d) {return d.x; }) | |
.attr("cy", function(d) { return d.y; }) | |
.attr("r", radiusDrag) | |
.call(mtouch); | |
svg.selectAll(".target-circle") | |
.data(targetCircle[0]) | |
.enter() | |
.append('circle') | |
.attr("class", "target-circle") | |
.attr("cx", function(d) { return d.x; }) | |
.attr("cy", function(d) { return d.y; }) | |
.attr("r", radiusTarget) | |
}; | |
///////////////////////////////////// | |
//----HOLD + TAP functionality | |
///////////////////////////////////// | |
//For now I just want to have some feedback for each event | |
function tapped(d) { | |
console.log('tap'); | |
} | |
function touched() { | |
console.log('touched'); | |
} | |
function released() { | |
console.log('released'); | |
} | |
// THIS IS THE PART FROM THE MENTIONED EXAMPLE | |
/// Based on the d3.behavior.drag and d3.behavior.zoom. | |
mtouch_events = function() { | |
var event = d3_eventDispatch(mtouch, "tap", "dbltap", "hold", "drag", "mdrag", 'touch', 'release') | |
,fingers = [] // array of augmented touches = fingers | |
,id2finger = {} // maps ids to fingers | |
,last_taps = [] // [{timeStamp: xxx, pos: [x,y]}, ...], used to detect dbltaps | |
,mouse_id = 'mouse' | |
,tap_max_time = 250 | |
,tap_max_dist2 = 10*10 | |
,hold_time = 500 | |
,hold_max_dist2 = 10*10 | |
,dbltap_max_delay = 400 | |
,dbltap_max_dist = 20; | |
function mtouch(select) { | |
select.on("touchstart.mtouch", touchstarted) | |
.on("mousedown.mtouch", mousedown) | |
.on("touchmove.mtouch", touchmoved) | |
.on("touchend.mtouch", touchended) | |
.on("touchcancel.mtouch", touchended); | |
} | |
mtouch.call = function(f) { | |
f.apply(mtouch, arguments); return this; | |
} | |
/// On mousedown, start listening for mousemove and mouseup events on the | |
/// whole window. Also call the touchstarted function. If it was not the left | |
/// mousebutton that was pressed, do nothing. | |
function mousedown() { | |
if (!detectLeftButton(d3.event)) return; | |
var w = d3.select(window); | |
var thiz = this, argumentz = arguments; | |
w.on("mousemove.mtouch", function() { touchmoved.apply(thiz, argumentz) }); | |
w.on("mouseup.mtouch", function() { | |
w.on("mousemove.mtouch", null); | |
w.on("mouseup.mtouch", null); | |
touchended.apply(thiz, argumentz); | |
}); | |
touchstarted.apply(this, arguments); | |
} | |
function touchstarted() { | |
d3.event.preventDefault(); | |
var target = this | |
,event_ = event.of(target, arguments) | |
,touches = get_changed_touches(); | |
for (var i=0,N=touches.length; i<N; i++) { | |
var finger = new Finger(touches[i].identifier, event_, target); | |
fingers.push(finger); | |
id2finger[touches[i].identifier] = finger; | |
event_({type: 'touch', finger: finger, fingers: fingers}); | |
} | |
} | |
function touchmoved() { | |
d3.event.preventDefault(); | |
var target = this | |
,event_ = event.of(target, arguments) | |
,touches = get_changed_touches(); | |
for (var i=0,N=fingers.length; i<N; i++) fingers[i].changed = false; | |
var df = []; | |
for (var i=0,N=touches.length; i<N; i++) { | |
var finger = id2finger[touches[i].identifier]; | |
if (!finger) continue; | |
finger.move(event_); | |
df.push(finger); | |
} | |
event_({type: 'mdrag', dragged_fingers: df, fingers: fingers}); | |
} | |
function touchended() { | |
d3.event.preventDefault(); | |
var target = this | |
,event_ = event.of(target, arguments) | |
,touches = get_changed_touches(); | |
for (var i=0,N=touches.length; i<N; i++) { | |
var finger = id2finger[touches[i].identifier]; | |
if (!finger) continue; | |
finger.end(event_); | |
delete id2finger[touches[i].identifier]; | |
fingers = d3.values(id2finger); | |
event_({type: 'release', finger: finger, fingers: fingers}); | |
} | |
} | |
function Finger(id, event, target) { | |
this.id = id; | |
this.target = target; | |
this.event = event; | |
this.parent = target.parentNode; | |
this.timeStamp0 = d3.event.timeStamp; | |
this.timeStamp = this.timeStamp0; | |
this.hold_timer = setTimeout(this.held.bind(this), hold_time); | |
this.pos = get_position(this.parent, this.id); | |
this.pos0 = [this.pos[0], this.pos[1]]; | |
this.dist_x = 0; // dx between current and starting point | |
this.dist_y = 0; | |
this.dx = 0; // dx in the last dragging step | |
this.dy = 0; | |
this.dt = 0; // dt in the last dragging step | |
this.changed = true; // used by gesture to check whether it needs to update | |
this.gesture = null; // is set when finger gets bound to a gesture | |
} | |
Finger.prototype.cancel_hold = function() { | |
if (this.hold_timer) clearTimeout(this.hold_timer); | |
this.hold_timer = null; | |
} | |
Finger.prototype.held = function() { | |
this.event({type: 'hold', id: this.id, fingers: fingers}); | |
this.hold_timer = null; | |
} | |
Finger.prototype.move = function(event) { | |
this.changed = true; | |
this.event = event; | |
var p = get_position(this.parent, this.id) | |
,t = d3.event.timeStamp; | |
this.dx = p[0] - this.pos[0]; | |
this.dy = p[1] - this.pos[1]; | |
this.dist_x = p[0] - this.pos0[0]; | |
this.dist_y = p[1] - this.pos0[1]; | |
this.pos = p; | |
this.dt = t-this.timeStamp; | |
this.timeStamp = t; | |
if (this.dist_x*this.dist_x+this.dist_y*this.dist_y > hold_max_dist2) { | |
this.cancel_hold(); | |
} | |
if (this.gesture) return; | |
event({type: 'drag', finger: this, x: this.pos[0], y: this.pos[1] | |
,dx: this.dx, dy: this.dy, fingers: fingers}); | |
} | |
Finger.prototype.end = function(event) { | |
var dt = d3.event.timeStamp - this.timeStamp0; | |
if (dt <= tap_max_time && (this.dist_x*this.dist_x+this.dist_y*this.dist_y) <= tap_max_dist2) { | |
if (match_tap(d3.event.timeStamp, this.pos[0], this.pos[1])) { | |
event({type: 'dbltap', finger: this, fingers: fingers}); | |
} else { | |
event({type: 'tap', finger: this, fingers: fingers}); | |
} | |
} | |
this.cancel_hold(); | |
} | |
function get_changed_touches() { | |
return d3.event.changedTouches || [{identifier: mouse_id}]; | |
} | |
function detectLeftButton(event) { | |
if ('buttons' in event) return event.buttons === 1; | |
else if ('which' in event) return event.which === 1; | |
else return event.button === 1; | |
} | |
/// Returns true if any tap in the last_taps list is spatially and temporally | |
/// close enough to the passed time and postion to count as a dbltap. If not, | |
/// the passed data is added as new tap. All taps that are too old are removed. | |
function match_tap(timeStamp, x, y) { | |
var idx = -1, pos = [x,y]; | |
last_taps = last_taps.filter(function (tap, i) { | |
if (timeStamp - tap.timeStamp <= dbltap_max_delay | |
&& get_distance(tap.pos, pos) <= dbltap_max_dist) idx = i; | |
return tap.timeStamp-timeStamp <= dbltap_max_delay && idx !== i; | |
}); | |
if (idx === -1) last_taps.push({timeStamp: timeStamp, pos: pos}); | |
return idx !== -1; | |
} | |
function get_position(container, id) { | |
if (id === mouse_id) return d3.mouse(container); | |
else return d3.touches(container).filter(function(p) { return p.identifier === id; })[0]; | |
} | |
function get_distance(p1, p2) { | |
return Math.sqrt((p1[0]-p2[0])*(p1[0]-p2[0]) + (p1[1]-p2[1])*(p1[1]-p2[1])); | |
} | |
return d3.rebind(mtouch, event, "on"); | |
}; | |
/////////////////////////// | |
// HERE IS THE MANUALLY ADDED REBIND FUNCTION | |
/////////////////////////// | |
// Copies a variable number of methods from source to target. | |
d3.rebind = function(target, source) { | |
var i = 1, n = arguments.length, method; | |
while (++i < n) target[method = arguments[i]] = d3_rebind(target, source, source[method]); | |
return target; | |
}; | |
// Method is assumed to be a standard D3 getter-setter: | |
// If passed with no arguments, gets the value. | |
// If passed with arguments, sets the value and returns the target. | |
function d3_rebind(target, source, method) { | |
return function() { | |
var value = method.apply(source, arguments); | |
return value === source ? target : value; | |
}; | |
} | |
/// Replication of the internal d3_eventDispatch method. | |
function d3_eventDispatch(target) { | |
var dispatch = d3.dispatch.apply(this, Array.apply(null, arguments).slice(1)); | |
dispatch.of = function(thiz, argumentz) { | |
return function(e1) { | |
try { | |
var e0 = | |
e1.sourceEvent = d3.event; | |
e1.target = target; | |
d3.event = e1; | |
dispatch[e1.type].apply(thiz, argumentz); | |
} finally { | |
d3.event = e0; | |
} | |
}; | |
}; | |
return dispatch; | |
} | |
var mtouch = mtouch_events() | |
.on("tap", tapped) | |
.on("touch", touched) | |
.on("release", released); | |
start_experiment(); | |
</script> | |
</body> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment