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

386*_*6er 6 javascript d3.js

我已经设置了这个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 );

}
Run Code Online (Sandbox Code Playgroud)

}

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

我想知道的是:如果你观察细胞移动,有些只会部分移动到下一个细胞.anoyne可以告诉我,为什么会这样?

Coo*_*lue 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);
};
Run Code Online (Sandbox Code Playgroud)

是一个证明这个概念的小提琴。


纯粹地道的d3版本

为了完整起见,这里使用 d3 方法。
我已尽力使其尽可能地惯用。要点是...

  1. 纯数据驱动
    数据被更新,可视化操作完全留给 d3 声明。
  2. 使用 d3 检测 svg 元素属性的更改并对其采取行动。这是通过在方法中
    使用复合函数并利用以下事实来完成的:更改的节点( 、或属性与更新数据不匹配的方块)由 d3 捕获退出选择。keyselection.data()xyfill
  3. 将更改的元素拼接到数据数组中,以便 d3 可以检测更改
    由于对数据数组元素的引用绑定到 DOM 元素,因此对数据的任何更改也将反映在selection.datum(). d3 使用key函数将数据值与 进行比较datum,以便将节点分类为更新、进入或退出。如果key创建了 a,这是数据/基准值的函数,则不会检测到数据更改。通过splice-ing 更改到数据数组中,所引用的值selection.datum()将与数据数组不同,因此数据更改将标记退出节点。
    通过简单地操作属性并在退出选择上放置转换而不是删除它,它本质上变成了“更改的”选择。
    仅当数据值是对象时这才有效。
  4. 并发转换
    命名转换用于确保 x 和 y 转换不会相互中断,但也有必要使用标记类属性来锁定转换元素。这是使用转换开始和结束事件来完成的。
  5. 动画帧
    d3.timer用于平滑动画和整理资源。d3Timer 在每个动画帧之前、更新转换之前回调以更新数据。
  6. 用于d3.scale.ordinal()管理定位
    这很棒,因为它每次都有效,您甚至不必考虑它。

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);
};
Run Code Online (Sandbox Code Playgroud)
$(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();
	});
});
Run Code Online (Sandbox Code Playgroud)

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

编辑:修复了锁定和解锁的问题。标记数据可能比将类写入 DOM 更好,但无论如何……这种方式很有趣。