Julia Park
Julia Park

Reputation: 69

How to show circles (created using JSON data) in reverse order using a delay function?

I'm trying to create a dynamic visualization using circles that 'spread' out over some amount of time and all of the circles have the same centerpoint.

I have a separate script that creates the circles and stores the data in a JSON file - the first circle in the JSON file is the smallest circle on top of the image linked above.

Please see code snippet below. Basically, the script appends the circle data into circles into an svg with visibility set to none. The script reveal the circles one by one.

In the appending function, I tried using the .lower() function to reverse the order that the circles are appended to the svg because if I were to append in the order that the JSON file is in, each consecutive circle would hide the one below it. But then the animation plots backwards, where the larger circle plots first.

In the revealing function, I then tried adding a similar '.lower()' function to the transition method so each consecutive circle would reveal behind the previously revealed circle but then the code breaks. I'm just at a loss here - any pointers would be much appreciated.

html,
body,
#svg {
  background-color: #FFFFFF;
}
<html>

<head>
  <meta charset="utf-8" />
  <title>Visualizer</title>
</head>

<body>
  <div>
    <button onclick="plotStatically(0, 0, 'testingcircle.json')">Draw Static &#9658;</button>

    <button onclick="plotConsecutively(0, 0, 'testingcircle.json')">Draw Dynamic &#9658;</button>
  </div>


  <script src="https://d3js.org/d3.v5.min.js" charset="utf-8"></script>

  <script>
    function plotConsecutively(x, y, nameFile) {

      d3.json(nameFile).then(function(data) {

        var svgHeight = window.innerHeight - 100;
        var svgWidth = window.innerWidth - 10;

        var svg = d3.select('body').append('svg')
          .attr('width', svgWidth)
          .attr('height', svgHeight);

        svg.selectAll("circle")
          .data(data)
          .enter()
          .append('circle')
          .attr('r', function(d) {
            return d.r;
          })
          .attr('cx', function(d) {
            return d.cx + x;
          })
          .attr('cy', function(d) {
            return d.cy + y;
          })
          .attr('fill', function(d) {
            return d.fill;
          })
          .attr('visibility', 'hidden')

        svg.selectAll("circle")
          .transition()
          .delay(function(d, i) {
            return 3.4 * i;
          })
          .duration(10)
          .attr('visibility', 'visible');
      })
    }


    function plotStatically(x, y, nameFile) {

      d3.json(nameFile).then(function(data) {

        var svgHeight = window.innerHeight - 100;
        var svgWidth = window.innerWidth - 10;

        var svg = d3.select('body').append('svg')
          .attr('width', svgWidth)
          .attr('height', svgHeight);

        svg.selectAll("circle")
          .data(data)
          .enter()
          .append('circle')
          .attr('r', function(d) {
            return d.r;
          })
          .attr('cx', function(d) {
            return d.cx;
          })
          .attr('cy', function(d) {
            return d.cy;
          })
          .attr('fill', function(d) {
            return d.fill;
          });
      })
    }
  </script>


</body>

</html>

Upvotes: 1

Views: 377

Answers (1)

lemming
lemming

Reputation: 1873

I think you were pretty much there.

As you said, the larger circles need to be appended to the svg first so that they don't block out the smaller circles beneath them. I think this is most easily done simply by reversing the order of the data array just after you get the results of the json file:

d3.json(nameFile).then(function(data) {

  data = data.reverse();
  ...

Then, in order to show the circles from the inside out, you can change your delay function so that the items at the end of the array that you want to show first (the smaller circles) have the smallest delay, and the items at the beginning of the array that you want to show last (the larger circles) have the largest delay.

The third argument to the delay() function is the NodesList containing all the selected DOM elements, so you can use the length property of that array in your calculations.

...
.delay(function(d, i, circleNodes) {
  return 3.4 * ((circleNodes.length - 1) - i);
})
...

let data = [
  {"r":5,"cx":100,"cy":100,"fill":"red"},       {"r":10,"cx":100,"cy":100,"fill":"magenta"},{"r":15,"cx":100,"cy":100,"fill":"orange"},{"r":20,"cx":100,"cy":100,"fill":"green"},{"r":25,"cx":100,"cy":100,"fill":"blue"}
];

data = data.reverse();

function plotConsecutively() {

    var svg = d3.select('#svg')
      .append('svg')
      .attr('width', 200)
      .attr('height', 200);

    svg.selectAll("circle")
      .data(data)
      .enter()
      .append('circle')
      .attr('r', function(d) {
        return d.r;
      })
      .attr('cx', function(d) {
        return d.cx;
      })
      .attr('cy', function(d) {
        return d.cy;
      })
      .attr('fill', function(d) {
        return d.fill;
      })
      .attr('visibility', 'hidden')

    svg.selectAll('circle')
      .transition()
      .delay(function(d, i, nodes) {
        return 150 * ((nodes.length - 1) - i);
      })
      .duration(10)
      .attr('visibility', 'visible');
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>

<button onclick="plotConsecutively()">Draw Dynamic &#9658;</button>

<div id="svg"></div>

Upvotes: 1

Related Questions