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

How can I limit the movement of elements in D3 Force?

本文关键字:元素 移动 D3 Force 何限制      更新时间:2023-09-26

我使用这个例子: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);
  })
}
@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;
}
<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>

限制拖动的方法:

首先,设置你的拖动函数:
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是一个很好的练习,每次处理一个问题,一个问题一个问题。所以,我建议你把你的第二个问题作为一个单独的问题。