使用触摸板为d3 v5创建保持+轻按行为

时间:2018-11-29 03:04:42

标签: javascript d3.js events tap touchpad

背景

我正在为大学创建一个实验,以比较不同的拖动技术。为此,我正在使用d3,我需要使用其他设备,其中之一是触摸板。

我的研究如此遥远

现在,一种技术要求在触摸板上双击并按住行为。我找到了this example made with d3 v3。这几乎是我要实现的行为(只是具有不同的视觉反馈)。

问题

我正在使用d3 v5,我知道我需要从d3 v3版本中手动添加d3.rebind()函数。但是,我仍然有问题。尝试在黑色圆圈上创建事件时收到以下错误(在此处根据rioV8的答案进行编辑):

Uncaught TypeError: Cannot read property 'apply' of undefined

因为我无法弄清所提供示例中的整个代码在做什么,所以我自己寻找解决方案时遇到了问题。如果有人可以给我一些提示,那就太好了。

现在,我只想针对每个事件console.log()taptouch

实现release语句

我的密码

Here is a link to my bl.ocks.org version of my code.

这是我的代码:

<!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>

1 个答案:

答案 0 :(得分:-1)

如果您selection.call(func),则d3选择是函数的第一个参数。

  function mtouch(select) {
    select.on("touchstart.mtouch", touchstarted)
        .on("mousedown.mtouch", mousedown)
        .on("touchmove.mtouch", touchmoved)
        .on("touchend.mtouch", touchended)
        .on("touchcancel.mtouch", touchended);
  }