逐个转换圆圈

3
我有以下的HTML结构。
<div id = 'divTest'>
  <div id = 'divSVG'>
  </div>
  <button id ='bStep' type="button">Step</button> 
  <button id = 'bRun'  type="button">Entire run</button> 
 </div>

我使用D3创建了10个随机位置的圆,这些圆位于一个想象的正方形内,该正方形的左上角位于(5, 5),边长为十。

 const n = 10; // number of circles
 //Creating random circles inside a square whose l = 10
 //The top left corner of the square is at (5, 5)
 const x_ini = 5;
 const y_ini = 5;
 const x_length = 10;
 const y_length = 10
 const dataset = [];

 for(let i = 0; i < n; i ++) {
   const randomNumberX = Math.random()
   const x = randomNumberX * x_length + x_ini;
   const randomNumberY = Math.random()
   const y = randomNumberY * y_length + y_ini;
   const pair = [x, y];
   dataset[i] = pair;
 }

 const svg = d3.select('#divSVG')
               .append('svg')
               .attr('width', 300)
               .attr('height', 300);

 const circles = svg.selectAll('.circleTest')
                    .data(dataset)
                    .enter()
                    .append('circle')
                    .attr('cx', d => d[0])
                    .attr('cy', d => d[1])
                    .attr('class', 'circleTest')
                    .attr('r', 1)
                    .attr('fill', 'black');

我给步进按钮添加了一个功能,可以将一个圆移动到另一个虚拟圆上,并更改其类。

//Moves one circle to an imaginary square whose top left corner is at (100, 5)
 d3.select('#bStep')
   .on('click', () => {
     const x_ini = 100;
     const y_ini = 5;
     const x_length = 10;
     const y_length = 10;
     const randomNumberX = Math.random()
     const x = randomNumberX * x_length + x_ini;
     const randomNumberY = Math.random()
     const y = randomNumberY * y_length + y_ini;
     const circle = d3.select('.circleTest')
                      .transition()
                      .duration(1000)
                      .attr('cx', x)
                      .attr('cy', y)
                      .attr('fill', 'red')
                      .attr('class', 'circleTest2')

});

我希望能够通过点击“整个运行”按钮,使所有圆圈不是同时移动,而是根据一些输入数据依次过渡。例如,基于“指令”向量,在第一个过渡中只移动一个圆圈,然后移动三个圆圈等等。我该如何实现这一点?

d3.select('#bRun')
  .on('click', () => {
    const instructions = [1, 3, 0, 2, 4, 1]
    // Move all circles based on some input like 'instructions'
  });

以下是一个可行的示例: https://jsfiddle.net/jandraor/91nwpb7a/42/

2个回答

1

以下是一种基于不同数组执行该过渡的方法。

const instructions = [1, 3, 0, 2, 4, 1];
var pointer = 0;

function moveCircles () {
    if(!instructions[pointer]) {
    console.log('No circles to be transitioned');
    setTimeout(function () { moveCircles(); }, 1000);
    return ;
  }
     // Move all circles based on some input like 'instructions'
   if(pointer > instructions.length-1) {
    console.log("No more instructions.");
    return ;
   }
   if(!d3.selectAll('circle.circleTest:not(.transitioned)').size()) {
    console.log('No more circles to be transitioned');
    return ;
   }

   // transition circles
   var circles = d3.selectAll('circle.circleTest:not(.transitioned)')
      .filter(function (d, i) {
           return i < instructions[pointer];
      });

   circles.transition()
     .duration(1000).on('end', function (d, i) {
     if(i === circles.size()-1) {
        pointer++;
        moveCircles();
      }
     }).attr('cx', x)
     .attr('cy', y)
     .attr('fill', 'red')
     .attr('class', 'transitioned');
}    

d3.select('#bRun')
 .on('click', () => {
        moveCircles();
 });

我正在为已经转换的圆形分配一个类名为 transitioned ,您可以通过任何方式重置它。

说明:

  1. 指向指令数组。
  2. 单击时,请检查指针是否超出指令数组的范围,或者没有更多要转换的圆形,或者 instructions [pointer] === 0。
  3. 选择所有没有 transitioned 类的圆形,根据 instructions [pointer] 进行过滤并转换它们。
  4. 在每种情况下都增加指针。
  5. #2 和 #3 中的递归调用逻辑。

这是一个工作示例:

              
const n = 10; // number of circles
//Creating random circles inside a square whose l = 10
//The top left corner of the square is at (5, 5)
const x_ini = 5;
const y_ini = 5;
const x_length = 10;
const y_length = 10
const dataset = [];
for(let i = 0; i < n; i ++) {
  const randomNumberX = Math.random()
  const x = randomNumberX * x_length + x_ini;
  const randomNumberY = Math.random()
  const y = randomNumberY * y_length + y_ini;
  const pair = [x, y];
  dataset[i] = pair;
}

const svg = d3.select('#divSVG')
              .append('svg')
              .attr('width', 300)
              .attr('height', 300);
              
const circles = svg.selectAll('.circleTest')
   .data(dataset)
   .enter()
   .append('circle')
   .attr('cx', d => d[0])
   .attr('cy', d => d[1])
   .attr('class', 'circleTest')
   .attr('r', 1)
   .attr('fill', 'black');
 
   // transition co-ordinates computation
    const x_ini_to = 100;
    const y_ini_to = 5;
    const randomNumberX = Math.random()
    const x = randomNumberX * x_length + x_ini_to;
    const randomNumberY = Math.random()
    const y = randomNumberY * y_length + y_ini_to;
     
 //Moves one circle to an imaginary square whose top left corner is at (100, 5)
 d3.select('#bStep')
   .on('click', () => {
     const circle = d3.select('.circleTest')
                      .transition()
                        .duration(1000)
                        .attr('cx', x)
                        .attr('cy', y)
                        .attr('fill', 'red')
                        .attr('class', 'circleTest2')
   
   });
      
 const instructions = [1, 3, 0, 2, 4, 1];
 var pointer = 0;
  
  function moveCircles () {
    if(!instructions[pointer]) {
       pointer++;
        console.log('No circles to be transitioned');
        setTimeout(function () { moveCircles(); }, 1000);
        return ;
      }
         // Move all circles based on some input like 'instructions'
       if(pointer > instructions.length-1) {
        console.log("No more instructions.");
        return ;
       }
       if(!d3.selectAll('circle.circleTest:not(.transitioned)').size()) {
        console.log('No more circles to be transitioned');
        return ;
       }
       
       // transition circles
       var circles = d3.selectAll('circle.circleTest:not(.transitioned)').filter(function (d, i) {
         return i < instructions[pointer];
       });
       
       circles.transition()
         .duration(1000).on('end', function (d, i) {
         if(i === circles.size()-1) {
            pointer++;
            moveCircles();
          }
         }).attr('cx', x)
         .attr('cy', y)
         .attr('fill', 'red')
         .attr('class', 'transitioned');
  }    
  
   d3.select('#bRun')
     .on('click', () => {
       moveCircles();
     });
<script src="https://d3js.org/d3.v4.min.js"></script>

<div id = 'divTest'>
  <div id = 'divSVG'>
  
  </div>
 <button id ='bStep' type="button">Step</button> 
<button id = 'bRun'  type="button">Entire run</button> 
</div>

JSFIDDLE: https://jsfiddle.net/shashank2104/91nwpb7a/71/

编辑:我错了,我以为每次点击都要使圆圈过渡,但整个运行没有意义。无论如何,现在已经修复了。 希望这已经足够清楚了。如果不清楚,请告诉我。是的,请相应地匹配步骤按钮的点击以进行适当的同步。


1
这里有一个可能的答案。我创建了一个函数,每次递归调用下一个数组位置(arrayPos),直到指令数组被处理完毕。

d3.select('#bRun')
  .on('click', () => {
    const instructions = [1, 3, 0, 2, 4, 1]
    // Start the process with the array and the first array position - would have called it index but uses that in the filter.
    moveCircles(instructions, 0);
  });

/**
 * This function recursively calls itself until the instructions array is complete.
 */
function moveCircles(instructions, arrayPos) {
 const duration = 1000;
 if (arrayPos < instructions.length) {
   // If the instruction is zero we delay anyway - may not be desired behaviour.
   if (instructions[arrayPos] === 0) {
     setTimeout(() => {
       // Call this function with the next array position.
       moveCircles(instructions, arrayPos + 1)
      }, duration)
    }
    const x_ini = 100;
    const y_ini = 5;
    const x_length = 10;
    const y_length = 10;
    const circles = d3.selectAll('.circleTest').filter( (value, index) => {
     return index < instructions[arrayPos]
    })

    circles.transition()
         .duration(duration)
         .on('end', () => {
           // Call this function with the next array position.
           moveCircles(instructions, arrayPos + 1)
         })
         .attr('cx', () => {
           return Math.random() * x_length + x_ini;
         })
         .attr('cy',  () => {
           return Math.random() * y_length + y_ini;
         })
         .attr('fill', 'red')
         .attr('class', 'circleTest2')
  }
}   

注释

  • 使用selectAll获取所有剩余的圆形,然后过滤它们以获得指令所需的大小数组。
  • 我将x和y的随机计算移动到attr函数中,以便每个圆形移动具有随机位置。
  • 当指令为零时,我添加了一个setTimeout与转换持续时间相同 - 这可能不是必需的,因此只需删除setTimeout并保留对moveCircles()的调用即可。
  • 原始的单步按钮使用仅包含一个指令的数组调用相同的moveCircles函数以移动一个圆形。

jsfiddle- https://jsfiddle.net/bryanwadd/hfd9onpv/


哈哈,我的错,我在每次点击时都有圆形过渡。不确定为什么会这样。 :P 无论如何,感谢你让我重新阅读问题。 - Shashank

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