Coola
Coola

Reputation: 3142

Create synchronous loading of data using D3.js and drawing following the loading

I have reached a stage where I believe I am not understanding the correct callback of events. I am utilizing d3.js v5 and my issue is as follows. I have a function which parses a tsv file and adds data to a global array selectedData using the d3.tsv function which utilizes promises within a function addData:

//function takes filename and data description to add to the selectedData global variable
function addData(filename, dataDesc) {
  d3.tsv(filename, type).then ((data) => {
    data.forEach(e => {
        var obj = {
            Title: dataDesc,
            ID: e.ID,
            Value: e.Value,
        }
        selectedData.push(obj);
    });
  });
}

function type(d) {
  d.Value = +d.Value;
}

Now I use this function to either add a single data file or can call it multiple times to add data from multiple data files into this global variable. The global variable is utilized for a number of other functions but as a start it is used to produce a bar chart.

For adding a single file, I would like to produce a bar chart after adding the data to the global variable. To do so I use the following function:

//functions adds one data file
function addOneData() {
  filename = userfile; //gets the user file name based on <input> element using the FileReader api
  dataDesc = document.getElementById('file_title').value; //gets the Descriptive Title of the data
  addData(filename, dataDesc); //add data to the global variable of selectedData
  drawBarChart(); //draws a grouped bar chart based on the global array of selectedData
}

I have a similar function which loops through an array of multiple data files as well to add them to the global variable with different filenames and dataDesc. For the multiple files I do not want the drawing to happen after each file, which is why the drawBarChart function is not a part of the addData function.

Now my issue is that the functions of addData and drawBarChart are running asynchronously and therefore drawBarChart runs much before the data is added to the selectedData global variable. So my question is, how can I make this either synchronous or use the power of promises? I have been trying promises and async/await but it feels like I am missing out on some key concepts. Any help is appreciated. Thank you.

Upvotes: 1

Views: 1236

Answers (1)

Gerardo Furtado
Gerardo Furtado

Reputation: 102174

"Create synchronous loading of data" is something that doesn't make much sense in this case... Instead of that, accept the nature of asynchronous code.

Also, you said:

For the multiple files I do not want the drawing to happen after each file, which is why the drawBarChart function is not a part of the addData function.

But that's what your addOneData function is doing right now: it's calling drawBarChart for each file (wrongly, as you know, due to the asynchronous nature of addData).

So, let's fix your two problems, the asynchronous code and calling drawBarChart after loading multiple files.

1. Asynchronous code

An easy solution is passing drawBarchart as a callback in another then method, like this:

function addData(filename, dataDesc, callback) {
  d3.json(filename).then((data) => {
    data.forEach(e => {
        var obj = {
            Title: dataDesc,
            Value: e
        }
        selectedData.push(obj);
    });
  }).then(callback)
};

Here is a demo with a very simple JSON I created online (which requires d3.json, but the general principle is the same of your d3.tsv and it works just as a TSV for your case):

var selectedData = [];

addOneData();

function addData(filename, dataDesc, callback) {
  d3.json(filename).then((data) => {
    data.forEach(e => {
      var obj = {
        Title: dataDesc,
        Value: e
      }
      selectedData.push(obj);
    });
  }).then(callback)
};

function addOneData() {
  filename = "https://api.myjson.com/bins/m1vsl";
  dataDesc = "FooBar"
  addData(filename, dataDesc, drawBarChart);
};

function drawBarChart() {
  console.log(selectedData)
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>

2. Multiple files

As you mentioned you may have many files, but you want to call drawBarChart only once. In that case, use Promise.all and an array with your filenames:

function addData(filenames, dataDesc, callback) {
  Promise.all(filenames.map(d => d3.json(d))).then((alldata) => {
    alldata.forEach(data => {
      data.forEach(e => {
        var obj = {
          Title: dataDesc,
          Value: e
        }
        selectedData.push(obj);
      });
    });
  }).then(callback)
};

Here is the demo:

var selectedData = [];

addAllData();

function addData(filenames, dataDesc, callback) {
  Promise.all(filenames.map(d => d3.json(d))).then((alldata) => {
    alldata.forEach(data => {
      data.forEach(e => {
        var obj = {
          Title: dataDesc,
          Value: e
        }
        selectedData.push(obj);
      });
    });
  }).then(callback)
};

function addAllData() {
  filenames = ["https://api.myjson.com/bins/m1vsl", "https://api.myjson.com/bins/rncvp"];
  dataDesc = "FooBar";
  addData(filenames, dataDesc, drawBarChart);
};

function drawBarChart() {
  console.log(selectedData)
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>

Upvotes: 5

Related Questions