score:0

The problem is that onrendered is called when all the svg manipulations are done. But the transitions still have to be started. You have to wait till the transitions are finished.

The naive way is to get the transition after a call to c.hide or c.show but it is not attached yet. You have to wait till the first idle processing is done and the first transition tick is processed. You can do that by using a timeout of a few milisecond, then use the attached transition and hook a follow-up on to it and wait for the start of it to call a function to be handled when transition ends. This is done with the following function.

function atEndOf(dataN, action) {
  setTimeout(function() {
    var transition = d3.active(d3.select(".bb-chart-line.bb-target.bb-target-"+dataN).node());
    if (!transition) { return; } // no transition running
    transition.transition().on("start", action);
  }, 10);
}

To know when the hide and show transitions are done you can build an atEndOf chain

// wait till main animation is finished
setTimeout(function() {
  c.hide('data1');
  atEndOf('data1', function () {
    console.log('should be d1=0,d2=1');
    showOpacity();
    c.hide('data2');
    atEndOf('data2', function () {
      console.log('should be d1=0,d2=0');
      showOpacity();
    });
  });
}, 6000);

I don't know why but this trick does not work for the main start up draw transition. I get the error "too late: already started"

This uses the following function

function showOpacity() {
  var data1op = d3.select(".bb-chart-line.bb-target.bb-target-data1").style("opacity");
  var data2op = d3.select(".bb-chart-line.bb-target.bb-target-data2").style("opacity");
  console.log(`data1 op: ${data1op} --- data2 op: ${data2op}`);
}

Because the show and hide transitions use the default 1s duration it is hard to see that we wait till the end of the transition. To change the duration of these transitions we need to decorate the ChartInternal.endAll() method. Here I set a duration of 5000ms to all transitions.

var bb_endall = bb.chart.internal.fn.endall;
bb.chart.internal.fn.endall = function (transition, callback) {
  transition.duration(5000);
  bb_endall(transition, callback);
};

I tried to use another decoration that uses the atEndOf technique to call a user defined function but I could not get it to work.

A complete example with all transitions set to a duration of 5000ms

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <script src="https://d3js.org/d3.v4.min.js"></script>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/billboard.js/dist/billboard.min.css" />
  <script src="https://cdn.jsdelivr.net/npm/billboard.js/dist/billboard.pkgd.min.js"></script>
  <title>JS Bin</title>
</head>

<body>
 <div id="chart"></div>
<script>
  var bb_endall = bb.chart.internal.fn.endall;
  bb.chart.internal.fn.endall = function (transition, callback) {
    transition.duration(5000);
    bb_endall(transition, callback);
  };

  //console.log('should be d1=1,d2=1');
  var c = bb.generate({
    bindto: "#chart",
    size: {
      width: 500,
      height: 250
    },
    // transition: 0,
    transition: {duration: 5000},
    data: {
      x: "x",
      columns: [
        ["x", "2013-01-01", "2013-01-02", "2013-01-03", "2013-01-04", "2013-01-05", "2013-01-06"],
        ["data1", 30, 200, 100, 400, 150, 250],
        ["data2", 130, 340, 200, 500, 250, 350],
      ]
    },
    axis: {
      x: {
        type: "timeseries",
        tick: {
          format: "%Y-%m-%d"
        }
      }
    },
    onrendered: function() {
      // not usefull for end of transition
    }
  });
  function showOpacity() {
    var data1op = d3.select(".bb-chart-line.bb-target.bb-target-data1").style("opacity");
    var data2op = d3.select(".bb-chart-line.bb-target.bb-target-data2").style("opacity");
    console.log(`data1 op: ${data1op} --- data2 op: ${data2op}`);
  }
  function atEndOf(dataN, action) {
    setTimeout(function() {
      var transition = d3.active(d3.select(".bb-chart-line.bb-target.bb-target-"+dataN).node());
      if (!transition) { return; } // no transition running
      transition.transition().on("start", action);
    }, 10);
  }

  // wait till main animation is finished
  setTimeout(function() {
    c.hide('data1');
    atEndOf('data1', function () {
      console.log('should be d1=0,d2=1');
      showOpacity();
      c.hide('data2');
      atEndOf('data2', function () {
        console.log('should be d1=0,d2=0');
        showOpacity();
      });
    });
  }, 6000);


//   setTimeout(function(){console.log('should be d1=0,d2=1');c.hide('data1')},500);
//   setTimeout(function(){console.log('should be d1=0,d2=0');c.hide('data2')},1000);
//   setTimeout(function(){console.log('should be d1=1,d2=0');c.show('data1')},1500);
//   setTimeout(function(){console.log('should be d1=1,d2=1');c.show('data2')},2000);

//   setTimeout(function(){console.log('more waiting between hide/show')},3500);

//   setTimeout(function(){console.log('should be d1=0,d2=1');c.hide('data1')},5000);
//   setTimeout(function(){console.log('should be d1=0,d2=0');c.hide('data2')},6000);
//   setTimeout(function(){console.log('should be d1=1,d2=0');c.show('data1')},7000);
//   setTimeout(function(){console.log('should be d1=1,d2=1');c.show('data2')},8000);
  </script>
</body>

</html>

There is some code in billboard.js that calls onrendered when there is a TabVisible. It will be postponed to when the transition has finished. I have no clue yet when that is the case.


Related Query

More Query from same tag