2

This is probably a really simple question but I can't seem to figure out how to plot one circle after another (sequentially, with some delay) using d3.js. The circles would then stay on the screen. I'm reading in data from a JSON file and it looks like this:

[{"r":1.2672526041666667,"cx":0,"cy":672.9303022519051,"fill":"rgb(252, 243, 228)"},{"r":1.2672526041666667,"cx":1,"cy":672.9303022519051,"fill":"rgb(252, 243, 228)"},{"r":1.2672526041666667,"cx":2,"cy":672.9303022519051,"fill":"rgb(252, 243, 228)"},{"r":1.2672526041666667,"cx":3,"cy":672.9303022519051,"fill":"rgb(252, 243, 228)"}.....]

and so forth. Json file located here: https://berkeley.box.com/s/4egj1ugr3jm2yp1htoyk8qmtv8avvosb

I've tried so many things!

I've tried using transition/duration but that doesn't seem to be working... I've tried a forEach loop to no avail... I've tried an .each loop as well. I've also tried both methods shown here: How do I loop through or enumerate a JavaScript object?

but I can't get either working. I've also tried looking at this solution:

How to draw circles at different times with D3js?

But I can't seem to plot anything after about 7 hours of trying different things. Here's my code...

<head>
<meta charset="utf-8" />
<title>Laughter Visualizer</title>
<style>
html, body, #svg {
  background-color: #FFFFFF;
}


</style>
</head>

<body>
  <audio id="audioElement" src="laugh_8.mp3" type="audio/mp3"></audio>

  <div>
    <button onclick="plotPoints(0, 0)">Draw Points &#9658;</button>
  </div>


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


<script>


  function plotPoints(p1, counter) {

    d3.json("test2.json").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>
Gerardo Furtado
  • 100,839
  • 9
  • 121
  • 171
Julia Park
  • 69
  • 5
  • Can you please post a working example of your code? At the moment it seems not to work at all. – obscure May 23 '19 at 00:38
  • Please explain what do you mean by *"one after another"*. If you want to keep all the circles in the screen, a simple transition is the solution. However, if you want to move a single circle from one datapoint to the next (what your code, if working, would do), you need a different solution. – Gerardo Furtado May 23 '19 at 00:42
  • @obscure I've edited it slightly to produce something visible on the screen now. Also, the code references a local json file located here: https://berkeley.box.com/s/4egj1ugr3jm2yp1htoyk8qmtv8avvosb – Julia Park May 23 '19 at 00:54
  • @GerardoFurtado Thanks for the question - I've clarified in my post. I'm looking for the circles to stay, but I've tried using transition about 20 different ways and I can't seem to get it to work! – Julia Park May 23 '19 at 00:54

3 Answers3

2

Given your comment...

I'm looking for the circles to stay...

...the idiomatic D3 solution is using a simple transition, using the indices to set the delay. For instance, plotting one circle every 100 milliseconds:

.delay(function(_, i) {
    return i * 100;
})

Regarding the transition itself, you could do it in several different ways, for instance increasing the radiuses. My solution here is just plotting everything and transition the opacity from 0 to 1.

I also created some scales, so your data values don't need to correspond to SVG coordinates:

const xDomain = d3.extent(data, function(d) {
  return d.cx
});

const yDomain = d3.extent(data, function(d) {
  return d.cy
});

const xScale = d3.scaleLinear()
  .range([0, width])
  .domain(xDomain);

const yScale = d3.scaleLinear()
  .range([0, height])
  .domain(yDomain);

Here is a demo using your data:

const data = [{
  "r": 1.2672526041666667,
  "cx": 0,
  "cy": 672.9303022519051,
  "fill": "rgb(252, 243, 228)"
}, {
  "r": 1.2672526041666667,
  "cx": 1,
  "cy": 672.9303022519051,
  "fill": "rgb(252, 243, 228)"
}, {
  "r": 1.2672526041666667,
  "cx": 2,
  "cy": 672.9303022519051,
  "fill": "rgb(252, 243, 228)"
}, {
  "r": 1.2672526041666667,
  "cx": 3,
  "cy": 672.9303022519051,
  "fill": "rgb(252, 243, 228)"
}, {
  "r": 3.9895833333333335,
  "cx": 4,
  "cy": 661.4035574412531,
  "fill": "rgb(247, 231, 205)"
}, {
  "r": 3.9895833333333335,
  "cx": 5,
  "cy": 661.4035574412531,
  "fill": "rgb(247, 231, 205)"
}, {
  "r": 3.9895833333333335,
  "cx": 6,
  "cy": 661.4035574412531,
  "fill": "rgb(247, 231, 205)"
}, {
  "r": 5.073893229166667,
  "cx": 7,
  "cy": 660.5638673253352,
  "fill": "rgb(244, 226, 195)"
}, {
  "r": 5.073893229166667,
  "cx": 8,
  "cy": 660.5638673253352,
  "fill": "rgb(244, 226, 195)"
}, {
  "r": 5.6962890625,
  "cx": 9,
  "cy": 661.1542183362859,
  "fill": "rgb(243, 223, 190)"
}, {
  "r": 5.6962890625,
  "cx": 10,
  "cy": 661.1542183362859,
  "fill": "rgb(243, 223, 190)"
}, {
  "r": 5.6962890625,
  "cx": 11,
  "cy": 661.1542183362859,
  "fill": "rgb(243, 223, 190)"
}, {
  "r": 5.6962890625,
  "cx": 12,
  "cy": 661.1542183362859,
  "fill": "rgb(243, 223, 190)"
}, {
  "r": 6.125651041666667,
  "cx": 13,
  "cy": 661.8292769334325,
  "fill": "rgb(242, 221, 186)"
}, {
  "r": 6.125651041666667,
  "cx": 14,
  "cy": 661.8292769334325,
  "fill": "rgb(242, 221, 186)"
}, {
  "r": 6.422526041666667,
  "cx": 15,
  "cy": 661.5401588106098,
  "fill": "rgb(241, 219, 184)"
}, {
  "r": 6.422526041666667,
  "cx": 16,
  "cy": 661.5401588106098,
  "fill": "rgb(241, 219, 184)"
}, {
  "r": 6.422526041666667,
  "cx": 17,
  "cy": 661.5401588106098,
  "fill": "rgb(241, 219, 184)"
}, {
  "r": 6.422526041666667,
  "cx": 18,
  "cy": 661.5401588106098,
  "fill": "rgb(241, 219, 184)"
}, {
  "r": 6.770182291666667,
  "cx": 19,
  "cy": 658.8530307401353,
  "fill": "rgb(241, 218, 181)"
}, {
  "r": 6.770182291666667,
  "cx": 20,
  "cy": 658.8530307401353,
  "fill": "rgb(241, 218, 181)"
}, {
  "r": 6.770182291666667,
  "cx": 21,
  "cy": 658.8530307401353,
  "fill": "rgb(241, 218, 181)"
}, {
  "r": 7.0205078125,
  "cx": 22,
  "cy": 656.2237523376763,
  "fill": "rgb(240, 217, 179)"
}, {
  "r": 7.0205078125,
  "cx": 23,
  "cy": 656.2237523376763,
  "fill": "rgb(240, 217, 179)"
}, {
  "r": 7.0205078125,
  "cx": 24,
  "cy": 656.2237523376763,
  "fill": "rgb(240, 217, 179)"
}, {
  "r": 7.144205729166667,
  "cx": 25,
  "cy": 654.865008125636,
  "fill": "rgb(240, 216, 178)"
}, {
  "r": 7.144205729166667,
  "cx": 26,
  "cy": 654.865008125636,
  "fill": "rgb(240, 216, 178)"
}, {
  "r": 7.144205729166667,
  "cx": 27,
  "cy": 654.865008125636,
  "fill": "rgb(240, 216, 178)"
}, {
  "r": 7.349934895833333,
  "cx": 28,
  "cy": 653.0513751716197,
  "fill": "rgb(239, 215, 176)"
}, {
  "r": 7.349934895833333,
  "cx": 29,
  "cy": 653.0513751716197,
  "fill": "rgb(239, 215, 176)"
}, {
  "r": 7.7802734375,
  "cx": 30,
  "cy": 648.4655453746704,
  "fill": "rgb(238, 213, 172)"
}, {
  "r": 7.7802734375,
  "cx": 31,
  "cy": 648.4655453746704,
  "fill": "rgb(238, 213, 172)"
}, {
  "r": 7.7802734375,
  "cx": 32,
  "cy": 648.4655453746704,
  "fill": "rgb(238, 213, 172)"
}, {
  "r": 7.7802734375,
  "cx": 33,
  "cy": 648.4655453746704,
  "fill": "rgb(238, 213, 172)"
}, {
  "r": 8.924153645833334,
  "cx": 34,
  "cy": 646.4654507872818,
  "fill": "rgb(236, 208, 162)"
}, {
  "r": 8.924153645833334,
  "cx": 35,
  "cy": 646.4654507872818,
  "fill": "rgb(236, 208, 162)"
}, {
  "r": 8.924153645833334,
  "cx": 36,
  "cy": 646.4654507872818,
  "fill": "rgb(236, 208, 162)"
}, {
  "r": 8.924153645833334,
  "cx": 37,
  "cy": 646.4654507872818,
  "fill": "rgb(236, 208, 162)"
}, {
  "r": 10.550130208333334,
  "cx": 38,
  "cy": 642.0891082999074,
  "fill": "rgb(233, 200, 148)"
}, {
  "r": 10.550130208333334,
  "cx": 39,
  "cy": 642.0891082999074,
  "fill": "rgb(233, 200, 148)"
}, {
  "r": 10.550130208333334,
  "cx": 40,
  "cy": 642.0891082999074,
  "fill": "rgb(233, 200, 148)"
}, {
  "r": 11.846028645833334,
  "cx": 41,
  "cy": 638.9493098110339,
  "fill": "rgb(230, 194, 137)"
}, {
  "r": 11.846028645833334,
  "cx": 42,
  "cy": 638.9493098110339,
  "fill": "rgb(230, 194, 137)"
}, {
  "r": 13.046875,
  "cx": 43,
  "cy": 635.5438456420493,
  "fill": "rgb(227, 189, 127)"
}, {
  "r": 13.046875,
  "cx": 44,
  "cy": 635.5438456420493,
  "fill": "rgb(227, 189, 127)"
}, {
  "r": 13.046875,
  "cx": 45,
  "cy": 635.5438456420493,
  "fill": "rgb(227, 189, 127)"
}, {
  "r": 14.2236328125,
  "cx": 46,
  "cy": 631.6291795399932,
  "fill": "rgb(225, 183, 117)"
}, {
  "r": 14.2236328125,
  "cx": 47,
  "cy": 631.6291795399932,
  "fill": "rgb(225, 183, 117)"
}, {
  "r": 14.2236328125,
  "cx": 48,
  "cy": 631.6291795399932,
  "fill": "rgb(225, 183, 117)"
}, {
  "r": 15.346028645833334,
  "cx": 49,
  "cy": 626.1064208896337,
  "fill": "rgb(222, 178, 107)"
}, {
  "r": 15.346028645833334,
  "cx": 50,
  "cy": 626.1064208896337,
  "fill": "rgb(222, 178, 107)"
}, {
  "r": 15.346028645833334,
  "cx": 51,
  "cy": 626.1064208896337,
  "fill": "rgb(222, 178, 107)"
}, {
  "r": 16.297526041666668,
  "cx": 52,
  "cy": 620.3574348526613,
  "fill": "rgb(220, 174, 99)"
}, {
  "r": 16.297526041666668,
  "cx": 53,
  "cy": 620.3574348526613,
  "fill": "rgb(220, 174, 99)"
}, {
  "r": 16.297526041666668,
  "cx": 54,
  "cy": 620.3574348526613,
  "fill": "rgb(220, 174, 99)"
}, {
  "r": 16.778645833333332,
  "cx": 55,
  "cy": 617.0644627244038,
  "fill": "rgb(219, 172, 95)"
}, {
  "r": 16.778645833333332,
  "cx": 56,
  "cy": 617.0644627244038,
  "fill": "rgb(219, 172, 95)"
}, {
  "r": 16.778645833333332,
  "cx": 57,
  "cy": 617.0644627244038,
  "fill": "rgb(219, 172, 95)"
}, {
  "r": 16.83203125,
  "cx": 58,
  "cy": 614.5978314122896,
  "fill": "rgb(219, 171, 94)"
}, {
  "r": 16.83203125,
  "cx": 59,
  "cy": 614.5978314122896,
  "fill": "rgb(219, 171, 94)"
}, {
  "r": 16.83203125,
  "cx": 60,
  "cy": 614.5978314122896,
  "fill": "rgb(219, 171, 94)"
}, {
  "r": 17.060221354166668,
  "cx": 61,
  "cy": 609.8278349138509,
  "fill": "rgb(219, 170, 92)"
}, {
  "r": 17.060221354166668,
  "cx": 62,
  "cy": 609.8278349138509,
  "fill": "rgb(219, 170, 92)"
}, {
  "r": 17.060221354166668,
  "cx": 63,
  "cy": 609.8278349138509,
  "fill": "rgb(219, 170, 92)"
}, {
  "r": 17.060221354166668,
  "cx": 64,
  "cy": 609.8278349138509,
  "fill": "rgb(219, 170, 92)"
}, {
  "r": 17.533854166666668,
  "cx": 65,
  "cy": 602.747264716075,
  "fill": "rgb(218, 168, 88)"
}, {
  "r": 17.533854166666668,
  "cx": 66,
  "cy": 602.747264716075,
  "fill": "rgb(218, 168, 88)"
}, {
  "r": 17.533854166666668,
  "cx": 67,
  "cy": 602.747264716075,
  "fill": "rgb(218, 168, 88)"
}, {
  "r": 18.1669921875,
  "cx": 68,
  "cy": 596.8306485811727,
  "fill": "rgb(216, 165, 83)"
}, {
  "r": 18.1669921875,
  "cx": 69,
  "cy": 596.8306485811727,
  "fill": "rgb(216, 165, 83)"
}, {
  "r": 18.333984375,
  "cx": 70,
  "cy": 594.4961471538654,
  "fill": "rgb(216, 165, 81)"
}, {
  "r": 18.333984375,
  "cx": 71,
  "cy": 594.4961471538654,
  "fill": "rgb(216, 165, 81)"
}, {
  "r": 18.333984375,
  "cx": 72,
  "cy": 594.4961471538654,
  "fill": "rgb(216, 165, 81)"
}, {
  "r": 18.333984375,
  "cx": 73,
  "cy": 594.4961471538654,
  "fill": "rgb(216, 165, 81)"
}, {
  "r": 18.0771484375,
  "cx": 74,
  "cy": 594.6126027167029,
  "fill": "rgb(217, 166, 83)"
}, {
  "r": 18.0771484375,
  "cx": 75,
  "cy": 594.6126027167029,
  "fill": "rgb(217, 166, 83)"
}, {
  "r": 18.0771484375,
  "cx": 76,
  "cy": 594.6126027167029,
  "fill": "rgb(217, 166, 83)"
}, {
  "r": 17.9443359375,
  "cx": 77,
  "cy": 594.2603597883598,
  "fill": "rgb(217, 166, 85)"
}, {
  "r": 17.9443359375,
  "cx": 78,
  "cy": 594.2603597883598,
  "fill": "rgb(217, 166, 85)"
}];

const width = 400,
  height = 200;

const svg = d3.select("body")
  .append("svg")
  .attr("width", width)
  .attr("height", height);

const xDomain = d3.extent(data, function(d) {
  return d.cx
});

const yDomain = d3.extent(data, function(d) {
  return d.cy
});

const xScale = d3.scaleLinear()
  .range([25, width -25])
  .domain(xDomain);

const yScale = d3.scaleLinear()
  .range([25, height - 25])
  .domain(yDomain);

const circles = svg.selectAll(null)
  .data(data)
  .enter()
  .append("circle")
  .style("opacity", 0)
  .attr("cx", function(d) {
    return xScale(d.cx)
  })
  .attr("cy", function(d) {
    return yScale(d.cy)
  })
  .attr("r", function(d) {
    return d.r
  })
  .style("fill", function(d) {
    return d.fill
  })
  .transition()
  .delay(function(_, i) {
    return i * 100;
  })
  .style("opacity", 1);
<script src="https://d3js.org/d3.v5.min.js"></script>
Gerardo Furtado
  • 100,839
  • 9
  • 121
  • 171
  • Thank you this worked like a charm. However, I'm having some difficulty running the HTML page in chrome. The buttons show up but the script doesn't appear to run. Any ideas...? – Julia Park May 24 '19 at 20:06
  • I can't help you without relevant info. The best idea is posting it as a new question, with all the necessary details. – Gerardo Furtado May 25 '19 at 00:01
0

Here is a simple D3-based solution:

const svg = d3.select('svg')

const circleData = [
    {id: 1, x: 100, y: 300, c: '#f00', r: 50, d: 500},
    {id: 2, x: 250, y: 100, c: '#ff0', r: 90, d: 1000},
    {id: 3, x: 300, y: 350, c: '#08f', r: 70, d: 1500},
]

svg.selectAll('circle')
    .data(circleData, data => data.id)
    .enter()
    .append('circle')
    .attr('cx', data => data.x)
    .attr('cy', data => data.y)
    .attr('r', data => data.r)
    .style('fill', data => data.c)
    .style('visibility', 'hidden')
    .transition()
    .delay(data => data.d)
    .style('visibility', 'visible')

See the demo in a fiddle: https://jsfiddle.net/mrovinsky/pbgac6jw/

Michael Rovinsky
  • 6,807
  • 7
  • 15
  • 30
-1

The problem is that you're handing over the complete data out of the json object to the selectAll() function of d3 to be drawn at once. Instead inside the callback function for d3.json("test2.json") assign the returned data to a global variable. Afterwards initiate a interval where you call svg.append('circle') - thus telling d3 to draw a circle after another.

<head>
<meta charset="utf-8" />
<title>Laughter Visualizer</title>
<style>
html, body, #svg {
  background-color: #FFFFFF;
}


</style>
</head>

<body>
  <audio id="audioElement" src="laugh_8.mp3" type="audio/mp3"></audio>

  <div>
    <button onclick="plotPoints(0, 0)">Draw Points &#9658;</button>
  </div>


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


<script>

var svg;
var circleData;
var index=0;
var interval;
function update()
{
      svg.append('circle')
          .attr('r', circleData[index].r)
          .attr('cx', circleData[index].cx)
          .attr('cy', circleData[index].cy)
          .attr('fill', circleData[index].fill);
          if(index+1<circleData.length)
          {
            index++;
            }
}
  function plotPoints(p1, counter) {

    d3.json("test2.json").then(function(data){
        circleData=data;
        var svgHeight = window.innerHeight - 100;
        var svgWidth = window.innerWidth - 10;

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

        interval=setInterval(update,100);


    })

  }



</script>
</body>
</html>
obscure
  • 11,916
  • 2
  • 17
  • 36
  • 2
    You just got rid of the most important, cornerstone feature of D3: data binding. There is no data bound to those circles. – Gerardo Furtado May 23 '19 at 01:28
  • 4
    terrible and unidiomatic d3 solution. If you don't use data binding, might as well not use d3 at all and do it with plain js at this point – Coderino Javarino May 23 '19 at 08:49