Создайте синхронную загрузку данных, используя D3.js и рисование после загрузки - PullRequest
1 голос
/ 18 июня 2019

Я достиг стадии, когда я считаю, что не понимаю правильного обратного вызова событий. Я использую d3.js v5, и моя проблема заключается в следующем. У меня есть функция, которая анализирует файл TSV и добавляет данные в глобальный массив selectedData, используя функцию d3.tsv, которая использует обещания в функции 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;
}

Теперь я использую эту функцию, чтобы либо добавить один файл данных, либо могу вызывать его несколько раз, чтобы добавить данные из нескольких файлов данных в эту глобальную переменную. Глобальная переменная используется для ряда других функций, но в качестве начала она используется для создания гистограммы.

Для добавления одного файла я хотел бы создать гистограмму после добавления данных в глобальную переменную. Для этого я использую следующую функцию:

//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
}

У меня есть похожая функция, которая циклически перебирает массив из нескольких файлов данных, чтобы добавить их в глобальную переменную с различными filenames и dataDesc. Для нескольких файлов я не хочу, чтобы рисование происходило после каждого файла, поэтому функция drawBarChart не является частью функции addData.

Теперь моя проблема заключается в том, что функции addData и drawBarChart работают асинхронно, и поэтому drawBarChart запускается задолго до того, как данные добавляются в глобальную переменную selectedData. Итак, мой вопрос: как я могу сделать это либо синхронно, либо использовать силу обещаний? Я пробовал обещания и async / await, но мне кажется, что я упускаю некоторые ключевые понятия. Любая помощь приветствуется. Спасибо.

1 Ответ

5 голосов
/ 18 июня 2019

«Создать синхронную загрузку данных» - это то, что не имеет особого смысла в этом случае ... Вместо этого примите природу асинхронного кода.

Также вы сказали:

Для нескольких файлов я не хочу, чтобы рисование происходило после каждого файла, поэтому функция drawBarChart не является частью функции addData.

Но именно это ваша функция addOneData делает прямо сейчас: она вызывает drawBarChart для каждого файла (как вы знаете, ошибочно из-за асинхронной природы addData).

Итак, давайте исправим две ваши проблемы: асинхронный код и вызов drawBarChart после загрузки нескольких файлов.

1. Асинхронный код

Простое решение - передать drawBarchart в качестве обратного вызова в другом методе then, например:

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

Вот демонстрация с очень простым JSON, который я создал онлайн (для этого требуется d3.json, но общий принцип такой же, как у вашего d3.tsv, и он работает так же, как TSV для вашего случая):

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. Несколько файлов

Как вы упомянули, у вас может быть много файлов, но вы хотите позвонить на drawBarChart только один раз. В этом случае используйте Promise.all и массив с вашими именами файлов:

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)
};

Вот демоверсия:

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>
...