为什么某些细胞不能完全移动?

6
我已经建立了这个 jsfiddle: http://jsfiddle.net/386er/dhzq6q6f/14/
var moveCell = function(direction) {

var cellToBeMoved = pickRandomCell();
var currentX = cellToBeMoved.x.baseVal.value; 
var currentY = cellToBeMoved.y.baseVal.value;
var change = getPlusOrMinus() * (cellSize + 1 );
var newX = currentX + change;
var newY = currentY + change;
var selectedCell = d3.select(cellToBeMoved);


if (direction === 'x') {
    selectedCell.transition().duration(1500)
        .attr('x', newX );
} else {
    selectedCell.transition().duration(1500)
        .attr('y', newY );

}

在moveCell函数中,我会随机选择一个单元格,请求其当前的x和y坐标,然后加上或减去其宽度或高度,将其移动到相邻的单元格。

我想知道的是:如果您观察单元格的移动,有些单元格只会部分地移动到下一个单元格。有人能告诉我为什么会这样吗?


2
这很酷,伙计。但抱歉,我不知道为什么有些单元格不能完全移动。肯定会有人知道的。我发现,根据高度、宽度和单元格大小的变化,问题变得更加明显;我认为这可能是原因。http://jsfiddle.net/Panomosh/dhzq6q6f/15/ - Josh Stevenson
1
我刚刚尝试移除了过渡效果,现在它可以正常工作了。这是朝着正确方向迈出的一步。 - jcuenod
1
我想是因为选择的单元格已经在移动,所以出现了这种情况。尝试为转换中的单元格添加标识,并在“pickRandomCell”中排除这些单元格。 - jcuenod
1个回答

3
在这种情况下,首先要做的是在你的过渡中加入.each("interrupt", function() { console.log("interrupted!") });。然后你就会看到问题所在。
如果你像selection.transition("name")那样命名过渡,它应该可以解决问题,但实际上并没有解决。
这意味着你必须按照@jcuenod建议的方式排除正在移动的元素。其中一种惯用方法是这样的...
if (direction === 'x') {
    selectedCell.transition("x").duration(1500)
      .attr('x', newX)
      .each("start", function () { lock.call(this, "lockedX") })
      .each("end", function () { unlock.call(this, "lockedX") });
} else {
    selectedCell.transition("y").duration(1500)
      .attr('y', newY)
      .each("start", function () { lock.call(this, "lockedX") })
      .each("end", function () { unlock.call(this, "lockedX") });
}

function lock(lockClass) {
    var c = { cell: false }; c[lockClass] = true;
    d3.select(this).classed(c)
};
function unlock(lockClass) {
    var c = { cell: this.classList.length == 1 }; c[lockClass] = false;
    d3.select(this).classed(c);
};

这里是一个演示概念的Fiddle。


Pure and idiomatic d3 version

为了完整起见,这里展示了使用 d3 的方法。
我尽可能地让它符合习惯用法。主要点如下:

  1. 数据驱动
    更新数据后,可视化操作完全交给 d3 声明。
  2. 使用 d3 检测和响应 SVG 元素属性的变化
    通过在 selection.data() 方法中使用复合 key 函数,并利用那些被捕捉到的发生改变的节点 (即 xyfill 属性不匹配更新的数据的方块) 会出现在退出选择器中这一事实来完成。
  3. 将更改的元素插入到数据数组中,以便 d3 可以检测更改
    由于被绑定到 DOM 元素上的数据数组元素的引用,任何对数据的更改都将反映在 selection.datum() 中。d3 使用一个关键函数来比较数据值和 datum,以便将节点分类为更新、插入或退出。如果使用的关键函数是基于数据/ datum 值的函数,则不会检测到数据更改。通过在数据数组中进行 splice 更改,selection.datum() 引用的值将与数据数组不同,因此数据更改将标记退出节点。
    仅通过操纵属性并对退出选择器进行转换而不删除它,就可以将其视为“更改”选择器。
    这仅适用于数据值是对象的情况。
  4. 并发过渡
    命名的变换用于确保 x 和 y 变换不会相互干扰,但还需要使用标签类属性来锁定过渡元素。这是通过过渡开始和结束事件完成的。
  5. 动画帧
    使用 d3.timer 来平滑动画和管理资源,d3Timer 在更新过程之前、在每个动画帧之前回调更新数据。
  6. 使用 d3.scale.ordinal() 来管理位置
    这很棒,因为它总是有效的,而且你甚至不必考虑。

$(function () {
 var container,
  svg,
  gridHeight = 800,
  gridWidth = 1600,
  cellSize, cellPitch,
  cellsColumns = 100,
  cellsRows = 50,
  squares,

  container = d3.select('.svg-container'),
  svg = container.append('svg')
   .attr('width', gridWidth)
   .attr('height', gridHeight)
   .style({ 'background-color': 'black', opacity: 1 }),

  createRandomRGB = function () {
   var red = Math.floor((Math.random() * 256)).toString(),
     green = Math.floor((Math.random() * 256)).toString(),
     blue = Math.floor((Math.random() * 256)).toString(),
     rgb = 'rgb(' + red + ',' + green + ',' + blue + ')';
   return rgb;
  },

  createGrid = function (width, height) {

   var scaleHorizontal = d3.scale.ordinal()
      .domain(d3.range(cellsColumns))
      .rangeBands([0, width], 1 / 15),
     rangeHorizontal = scaleHorizontal.range(),

     scaleVertical = d3.scale.ordinal()
      .domain(d3.range(cellsRows))
      .rangeBands([0, height]),
     rangeVertical = scaleVertical.range(),

     squares = [];
   rangeHorizontal.forEach(function (dh, i) {
    rangeVertical.forEach(function (dv, j) {
     var indx;
     squares[indx = i + j * cellsColumns] = { x: dh, y: dv, c: createRandomRGB(), indx: indx }
    })
   });

   cellSize = scaleHorizontal.rangeBand();
   cellPitch = {
    x: rangeHorizontal[1] - rangeHorizontal[0],
    y: rangeVertical[1] - rangeVertical[0]
   }

   svg.selectAll("rect").data(squares, function (d, i) { return d.indx })
    .enter().append('rect')
    .attr('class', 'cell')
    .attr('width', cellSize)
    .attr('height', cellSize)
    .attr('x', function (d) { return d.x })
    .attr('y', function (d) { return d.y })
    .style('fill', function (d) { return d.c });

   return squares;
  },

  choseRandom = function (options) {
   options = options || [true, false];
   var max = options.length;
   return options[Math.floor(Math.random() * (max))];
  },

  pickRandomCell = function (cells) {

    var l = cells.size(),
      r = Math.floor(Math.random() * l);

    return l ? d3.select(cells[0][r]).datum().indx : -1;

  };

 function lock(lockClass) {
  var c = { cell: false }; c[lockClass] = true;
  d3.select(this).classed(c)
 };
 function unlock(lockClass) {
  var c = { cell: this.classList.length == 1 }; c[lockClass] = false;
  d3.select(this).classed(c);
 };

 function permutateColours() {
  var samples = Math.min(50, Math.max(~~(squares.length / 50),1)), s, ii = [], i, k = 0,
    cells = d3.selectAll('.cell');
  while (samples--) {
   do i = pickRandomCell(cells); while (ii.indexOf(i) > -1 && k++ < 5 && i > -1);
   if (k < 10 && i > -1) {
    ii.push(i);
    s = squares[i];
    squares.splice(i, 1, { x: s.x, y: s.y, c: createRandomRGB(), indx: s.indx });
   }
  }

 }

 function permutatePositions() {
  var samples = Math.min(20, Math.max(~~(squares.length / 100),1)), s, ss = [], d, m, p, k = 0,
    cells = d3.selectAll('.cell');
  while (samples--) {
   do s = pickRandomCell(cells); while (ss.indexOf(s) > -1 && k++ < 5 && s > -1);
   if (k < 10 && s > -1) {
    ss.push(s);
    d = squares[s];
    m = { x: d.x, y: d.y, c: d.c, indx: d.indx };
    m[p = choseRandom(["x", "y"])] = m[p] + choseRandom([-1, 1]) * cellPitch[p];

    squares.splice(s, 1, m);
   }
  }
 }

 function updateSquares() {
        //use a composite key function to transform the exit selection into
        // an attribute update selection
        //because it's the exit selection, d3 doesn't bind the new data
        // that's done manually with the .each 
  var changes = svg.selectAll("rect")
   .data(squares, function (d, i) { return d.indx + "_" + d.x + "_" + d.y + "_" + d.c; })
   .exit().each(function (d, i, j) { d3.select(this).datum(squares[i]) })

  changes.transition("x").duration(1500)
   .attr('x', function (d) { return d.x })
   .each("start", function () { lock.call(this, "lockedX") })
   .each("end", function () { unlock.call(this, "lockedX") })

  changes.transition("y").duration(1500)
   .attr('y', function (d) { return d.y })
   .each("start", function () { lock.call(this, "lockedY") })
   .each("end", function () { unlock.call(this, "lockedY") });

  changes.attr("stroke", "white")
   .style("stroke-opacity", 0.6)
   .transition("fill").duration(800)
    .style('fill', function (d, i) { return d.c })
    .style("stroke-opacity", 0)
    .each("start", function () { lock.call(this, "lockedFill") })
    .each("end", function () { unlock.call(this, "lockedFill") });
 }
 squares = createGrid(gridWidth, gridHeight);

 d3.timer(function tick() {
  permutateColours();
  permutatePositions();
  updateSquares();
 });
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
  <div class="svg-container"></div>

注意:需要 d3 版本 3.5.5 才能运行位置转换。

编辑:修复了锁定和解锁的问题。最好标记数据而不是将类写入 DOM,但无论如何...这种方法很有趣。


伙计,你让我的一天美好了!非常感谢你!我会仔细研究你的代码示例。 - 386er
1
没有问题,我无法停止盯着这个东西看... 如果将节点的datum()上锁状态存储起来,而不是使用classed写入DOM,动画运行速度会快两倍,但你必须管理多个锁,并且只有在所有过渡完成之后才能锁。这可以通过使用d3.map()作为每个节点的锁列表轻松管理。 - Cool Blue
实际上,让它快30%...我有点过于兴奋了。 :p - Cool Blue

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接