(D3.js) 如何更新动画中图形的每一步?

(D3.js) How to update every single step of the graph in animation?

首先,我想制作快速排序步骤的动画,以了解其行为。

快速排序算法很好(当然)来自参考书。

但我只能查看快速排序运动的开始和结束。 让我知道如何通过 d3.js 查看快速排序中的每一步。 (我是 JS 初学者。)

我的临时代码如下。 提前致谢。

index.html:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta http-equiv="Content-Type" content="text/html"; charset="utf-8" />
        <title>Sort Viz 03</title>
        <script src="https://d3js.org/d3.v7.min.js"></script>
        <script src="draw_functions.js"></script>
        <script src="quickSort.js"></script>
        <style>
            body {
                font-family: "Helvetica Neue", Helvetica, sans-serif;
                font-size: 30px;
                color: #333;
            }
            /* rect {
                fill: orange;
            } */
            #counter{
                width: 100px;
                height:100px;
                color: black;
                font: 500 60px system-ui;
            }
            div {
                font: 1000 30px system-ui;
            }
        </style>
    </head>

    <body>

        <h1 id="title0">Quick Sort</h1>
        <div id="counter0">0</div>
        <div id="chart0"></div>

        <script>
            var myData = d3.range(10);
            d3.shuffle(myData);
            initDraw(myData, "chart", 0);
            quickSort(myData, 0, myData.length, "chart", 0);
        </script>

    </body>
</html>

quickSort.js:

function quickSort(items, left, right, id, num) {

    if (right - left <= 1) return;

    var pivot_index = Math.floor((left + right) / 2);
    var pivot = items[pivot_index];

    swap(items, pivot_index, right - 1);
    redraw(items, id, num);

    var i = left;
    for (j = left; j < right - 1; ++j) {
        if (items[j] < pivot) {
            swap(items, i++, j);
            redraw(items, id, num);
        }
    } 
    swap(items, i, right - 1);
    redraw(items, id, num);

    quickSort(items, left, i, id, num);
    quickSort(items, i + 1, right, id, num);

    redraw(items, id, num);

    // console.log("#####", items ,"#####", left, right, num);
}


function swap(items, i, j) {

    // console.log("@@@@@", items, i, j, (i == j), "@@@@@")

    var tmp = items[i];
    items[i] = items[j];
    items[j] = tmp;

    console.log("@@@@@", items, i, j, (i == j), "@@@@@")

}

draw_functions.js:

const graphImageSizeX = 300;
const graphImageSizeY = 200;
const margin = {top: 20, right: 20, bottom: 20, left: 20};
const width = graphImageSizeX - margin.left - margin.right;
const height = graphImageSizeY - margin.top - margin.bottom;


var durationTime = 1000;



function initDraw(data, id, num) {


    console.log("#####", data ,"#####", id, num);


    var bandScale = d3.scaleBand()
    .domain(d3.range(data.length))
    .range([margin.left, width - margin.right])
    .paddingInner(0.05);

    d3.select("#" + id + num).append("svg")
    .attr("width", width)
    .attr("height", height)
    .append("g")
    .attr("transform", "translate(" + 0 + "," + 0 + ")");
  
    d3.select("#" + id + num).select("svg").select("g")
    .selectAll('rect')
    .data(d3.range(data.length))
    .join('rect')
    .attr("id", function(d, i) { return( "rect" + num + "_" + i ); })
    .attr('fill', "orange")
    .attr('width', bandScale.bandwidth())
    .attr('height', function(d, i) { return Math.floor(data[i] / data.length * height); })
    .attr('x', function(d) { return bandScale(d); })
    .attr("y", function(d, i) { return height - Math.floor(data[i] / data.length * height); })
}       


async function redraw(data, id, num) {


    console.log("#####", data ,"#####", id, num);


    var bandScale = d3.scaleBand()
    .domain(d3.range(data.length))
    .range([margin.left, width - margin.right])
    .paddingInner(0.05);

    d3.select("#" + id + num).select("svg").select("g")
    .selectAll('rect')
    // .data(data)
    // .enter()
    .transition()
    .duration(durationTime)
    .attr('fill', "brown")
    .attr('width', bandScale.bandwidth())
    .attr('height', function(d, i) { return Math.floor(data[i] / data.length * height); })
    .attr('x', function(d) { return bandScale(d); })
    .attr("y", function(d, i) { return height - Math.floor(data[i] / data.length * height); })
}       

此外:

我必须诚实地告诉你真相。这是我已经做过的。

Comparison

此示例在排序中使用“记录”逐步进行。但是,如您所见,我的归并排序失败了。

所以,我想寻找另一种方法,不使用“录制和重放”,而是简单地“挂钩”排序过程——如果可能的话——。

否则,某些排序方法将更难(重新)播放其动画,这不是“交换”类比所描述的。 (记录的数据会比较复杂。)

抱歉不够诚实。但是遇到这种情况怎么办呢?

提前致谢。

问题是您在调用 redraw() 时没有等待转换完成(它需要 1 秒,即 durationTime,而随后的 redraw 调用必须花费一些时间毫秒)。

解决方法是将所有步骤保存在一个数组中。然后在每次 redraw() 调用之间重新使用它重新绘制一个时间间隔。为此使用 setInterval

我创建了一个包含上述更改的工作 codepen

  1. steps.push 替换对 redraw() 的所有调用(参考:数组 push 方法)
  2. 排序完成后。调用重绘 steps.length 次,时间间隔大于 durationTime(参考:setInterval
  3. 完成重绘后,clearInterval 清理 setInterval(参考:clearInterval