如何限制D3 Force中元素的移动?

时间:2016-11-14 19:49:25

标签: javascript canvas d3.js

我使用此示例:https://codepen.io/AndrewGHC/pen/mPXjKr

我有两个问题:

1)如何限制拖放?例如,我想限制围绕圆圈移动50px。距离圆圈的起始位置不应超过50px。

2)刷新页面时,您会看到不同位置的元素。 一个圆可能是正确的,下一次是左边。如何避免这种情况?我想我需要设定起始位置。



// Request data, generate bg with Trianglify & set up loading function.

function slowPrint(tgt, i, msg, spd) {
  if (i < msg.length) {
    $(tgt).append(msg[i]);
    i++;
    var writeTimer = setTimeout(function() {
      slowPrint(tgt, i, msg, spd)
    }, spd);
  }
}

function writeThis(tgt, msg, spd) {
  if ($(tgt).html() === msg) {
    return;
  }
  $(tgt).html('');
  slowPrint(tgt, 0, msg, spd);
}

writeThis('#info', 'Loading . . .', 100);

var url = "https://raw.githubusercontent.com/AndrewGHC/kevin-bacon-number/master/kevinBacon.json";

d3.json(url, drawGraph);

// Credit to Trianglify @ https://github.com/qrohlf/trianglify

var pattern = Trianglify({
  height: $(document).height(),
  width: $(document).width(),
  cell_size: 40
});

document.body.style.backgroundImage = "url(" + pattern.png() + ")";

// Create the drawGraph callback

function drawGraph(err, data) {
  if (err) throw err;

  var width = $('#graph').width(),
    height = $('#graph').height();

  // Prepare the data for the force graph, beinning by creating an array of movies (strings)

  var movies = [];
  (function() {
    data.actors.forEach(function(actor) {
      actor.movies.forEach(function(movie) {
        if (movies.indexOf(movie) === -1) {
          movies.push(movie);
        }
      });
    });
  }())

  // Create the links array for the force graph, mapping actors to movies. This will draw a line between the two.

  var links = [];
  (function() {
    data.actors.forEach(function(actor, actorIndex) {
      actor.movies.forEach(function(movie, movieIndex) {
        links.push({
          "source": actorIndex,
          "target": data.actors.length + movies.indexOf(movie)
        });
      });
    });
  }())

  // Now prepare the nodes array, concatenating data.actors and the movies array. The order here is important, and movie indices must be converted into objects.

  var nodes = data.actors;
  movies.forEach(function(movie) {
    nodes.push({
      "movie": movie
    });
  });

  // Create the SVG canvas & force layout

  var canvas = d3.select('#graph')
    .append('svg')
    .attr("height", height)
    .attr("width", width);

  var force = d3.layout.force()
    .size([width, height])
    .nodes(nodes)
    .links(links)
    .linkDistance(50)
    .charge(function(d) {
      if (d.name === "Kevin Bacon") {
        return -1000;
      } else if (d.name) {
        return -(d.weight) * 50;
      }
      return -((d.weight * 50) * 5);
    })
    .gravity(0.1)
    .start();

  // Helper function to remove whitespace, later used for assigning IDs

  function rmWs(string) {
    if (typeof string !== 'string') {
      return false;
    }
    string = string.split(' ').join('');
    return string;
  }

  // Create the links

  var link = canvas.selectAll('.link')
    .data(links)
    .enter().append('line')
    .attr('class', 'link');

  // Create a colour scale for movie nodes. Find the min and max no. of links for the range of the colour domain.

  var arrMax = [];
  links.forEach(function(link) {
    arrMax.push(link.target.weight);
  });

  var colour = d3.scale.linear()
    .domain([1, d3.max(arrMax)])
    .range(["white", "black"])
    .interpolate(d3.interpolateHcl);

  // Set up the pop up on mouse hover



  // Call circles on SVG chart, with colours along a white - black gradient generated based on the max weight & variable sizing. Then place text on these movie elements.

  var circleRadius = 17;

  var circles = canvas.selectAll('.movies')
    .data(nodes)
    .enter()
    .append('circle')
    .attr('r', function(d, i) {
      if (d.name) {
        return circleRadius;
      }
      return circleRadius + (d.weight * 2);
    })
    .attr('stroke', '#777')
    .attr('stroke-width', '2px')
    .attr('fill', function(d, i) {
      return colour(d.weight) || 'black';
    })
    .call(force.drag)

  var text = canvas.selectAll('.moviesText')
    .data(nodes)
    .enter()
    .append('text')
    .attr('text-anchor', 'middle')
    .text(function(d) {
      return d.movie;
    });

  // Set up clip path for each forthcoming image node to clip rectangular images to circles. Then call images on the canvas.

  var clip = canvas.selectAll('clipPath')
    .data(nodes)
    .enter()
    .append('clipPath')
    .attr('id', function(d) {
      return rmWs(d.name) || rmWs(d.movie)
    })
    .append('circle')
    .attr('r', circleRadius);

  var imgWidth = 50,
    imgHeight = 50;

  var node = canvas.selectAll('.node')
    .data(nodes)
    .enter()
    .append('image')
    .attr('xlink:href', function(d) {
      return d.thumbnail;
    })
    .attr("class", "image")
    .attr("width", imgWidth)
    .attr("height", imgHeight)
    .attr("clip-path", function(d) {
      return "url(#" + (rmWs(d.name) || rmWs(d.movie)) + ")"
    })
    .call(force.drag);

  // Handle operations on each tick.

  force.on("tick", function() {

    link.attr("x1", function(d) {
        return d.source.x;
      })
      .attr("y1", function(d) {
        return d.source.y;
      })
      .attr("x2", function(d) {
        return d.target.x;
      })
      .attr("y2", function(d) {
        return d.target.y;
      });

    node.attr("x", function(d) {
        return d.x - (imgWidth / 2);
      })
      .attr("y", function(d) {
        return d.y - (imgHeight / 2);
      });

    clip.attr('cx', function(d) {
        return d.x;
      })
      .attr('cy', function(d) {
        return d.y;
      })

    circles.attr('cx', function(d) {
        return d.x;
      })
      .attr('cy', function(d) {
        return d.y;
      })

    text.attr('x', function(d) {
        return d.x;
      })
      .attr('y', function(d) {
        return d.y - 30;
      })
  });

  // When all initial calculations are done, print title to replace 'Loading . . .'

  force.on('end', function() {
    writeThis('#info', 'D3 Force Graph - Distance from Kevin Bacon', 100);
  })
}
&#13;
@import url(https://fonts.googleapis.com/css?family=Questrial' rel='stylesheet' type='text/css);
 #header {
  text-align: center;
  font-family: 'Jockey One', sans-serif;
}
#graph {
  margin: 15px auto;
  background: white;
  height: 750px;
  width: 750px;
  -webkit-box-shadow: 0px 0px 8px 2px rgba(0, 0, 0, 0.75);
  -moz-box-shadow: 0px 0px 8px 2px rgba(0, 0, 0, 0.75);
  box-shadow: 0px 0px 8px 2px rgba(0, 0, 0, 0.75);
}
.link {
  stroke: #777;
  stroke-width: 2px;
}
&#13;
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.3.1/d3.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="header">
  <h2 id="info"></h2>
</div>
<div id="graph"></div>
&#13;
&#13;
&#13;

1 个答案:

答案 0 :(得分:0)

以下是限制拖动的方法:

首先,设置拖动功能:

var drag = force.drag()
    .on("dragstart", dragstarted)
    .on("drag", dragged);

然后,在dragstarted中,获取当前的xy位置:

function dragstarted(d) {
    currentX = d.x;
    currentY = d.y;
}

最后,在dragged函数中,设置规则:

function dragged(d) {
    d.px = (d.px > currentX + 50) ? currentX + 50 : d.px;
    d.py = (d.py > currentY + 50) ? currentY + 50 : d.py;
    d.px = (d.px < currentX - 50) ? currentX - 50 : d.px;
    d.py = (d.py < currentY - 50) ? currentY - 50 : d.py;
}

这是笔:https://codepen.io/anon/pen/eBzyYd?editors=0010

PS:在这里,SO是一个很好的做法,一次处理1个问题,每个问题一个问题。所以,我建议你将第二个问题作为一个单独的问题发布。