Если вы не читали мою первую публикацию в блоге о данных в JavaScript, я настоятельно рекомендую вам вернуться и взглянуть на нее, прежде чем читать этот блог. В первой части было мало кода и много теории. Ожидайте, что эта часть углубится в гораздо больше кода. Во-первых, как и было обещано, мы будем создавать визуализации с помощью D3. Для этого нам нужно убедиться, что мы используем оптимальные структуры данных. В прошлый раз мы собрали наши данные в один большой объект. Это управляемо, но я думаю, что это будет намного эффективнее, если мы поместим его в массив объектов. Помните, что наш импортированный набор данных содержал кучу новостей, если читатель их помнил, и их точность. Наше намерение здесь состоит в том, чтобы создать массив объектов для каждого заголовка (A — K), подсчета, подсчета раз, когда он был вызван, и подсчета того, сколько раз они считают точными.

d3.csv('responses.csv', function(data){
 var arr = [];
for (i = 0; i < data.length; i++){
  if (arr.indexOf(data[i].headline) === -1) {
   arr.push(data[i].headline);
  }
 }
 for (j = 0; j < arr.length; j++){
  var currObj = data.filter(function(a) {
   return a.headline === arr[j]; 
  }, []);
  var reducedObj = currObj.reduce(function(a, b){
   a.count++;
   if (b.recalled_bool === "True") { 
    a.recalled_cnt++; 
   }
   if (b.accuracy_bool === "True") { 
    a.accuracy_cnt++; 
   }
   return a;
  }, { "headline": arr[j], "count": 0, "recalled_cnt": 0, "accuracy_cnt": 0 })
  ds.push(reducedObj);
 }
 renderGraph(ds);
});

Сначала мы инициализируем массив arr, в котором мы будем хранить уникальное значение для каждого заголовка [A, B, …, K]. Это будет использоваться в нашем основном цикле for ниже, чтобы мы могли создать объект для каждого индекса в нашем массиве. Перебирая весь наш необработанный набор данных, мы проверяем каждый элемент и помещаем все уникальные значения в массив arr.

Далее у нас есть основной цикл. Здесь мы проходим по каждому элементу только что созданного массива arr. Для каждого индекса в массиве применить фильтр к нашему набору данных и передать подмножество данных в currObj.

Если вы помните, фильтр был одним из важнейших компонентов управления данными. Чтобы иметь возможность потреблять данные, вам нужно иметь возможность фильтровать данные, потому что вам нужно создавать меньшие подмножества, которые точно определяют очень конкретную информацию, которую ищут ваши пользователи. Эта конкретная функция принимает текущий индекс набора данных и проверяет, равен ли он заголовку для текущей итерации цикла for.

var currObj = data.filter(function(a) {
   return a.headline === arr[j]; 
  }, []);

Поэтому для каждого заголовка мы создаем подмножество данных из основного набора данных. Следующая часть — это функция сокращения. Здесь мы фактически выравниваем наши данные и суммируем их. Помните, что мы собирались создать три метрики: количество, отозванные_число и точность_числа.

var reducedObj = currObj.reduce(function(a, b){
   a.count++;
   if (b.recalled_bool === "True") { 
    a.recalled_cnt++; 
   }
   if (b.accuracy_bool === "True") { 
    a.accuracy_cnt++; 
   }
   return a;
  }, { "headline": arr[j], "count": 0, "recalled_cnt": 0, "accuracy_cnt": 0 })
  ds.push(reducedObj);
 }

Наша функция сокращения принимает два параметра: a, который является значением из предыдущей итерации, и b, который является текущим данным, с которым мы работаем. Неуловимый второй параметр — функция сокращения (первая — это функция обратного вызова, которая фактически собирает данные) — это начальное значение. Итак, мы начинаем с объекта, который содержит заголовок для текущей итерации и наши три метрики, инициализированные нулями. При первом вызове обратного вызова он передаст этот объект как a и 0-й индекс как b. Мы увеличим count, а также, если это применимо, увеличим приписанный_счет и точность_cnt. Наконец, мы вернем a, который будет передан обратно в функцию при втором вызове и так далее до n-го вызова, который будет последним значением в массиве. После завершения мы поместим возвращенное значение (reducedObj) в наш массив ds, который является окончательным набором данных, с которым мы будем работать в D3.

Последнее, что мы делаем, это вызываем функцию renderGraph, передавая окончательный и полный массив ds. Я не буду рассматривать весь код D3, но добавлю его здесь для полноты картины:

function renderGraph (B){
 d3.select(".chart").selectAll("*").remove();
var x = d3.scaleLinear()
 .domain([0, d3.max(B)])
 .range([0, cfg.width]);
 
 var chart = d3.select(".chart")
 .attr("width", cfg.width)
 .attr("height", cfg.barHeight * B.length);
var bar = chart.selectAll("g")
 .data(B)
 .enter().append("g")
 .attr("transform", function(d, i) {
  return "translate(0," + i * cfg.barHeight + ")";
 });
bar.append("rect")
 .attr("width", function(d) { 
  return d.recalled_cnt / 5 + 10; 
 }).attr("height", cfg.barHeight - 1);
bar.append("text")
 .attr("x", function(d) {
  return d.recalled_cnt / 5; 
 })
 .attr("y", cfg.barHeight / 2)
 .attr("dy", ".35em")
 .text(function(d) {
  return d.recalled_cnt;
 })
};

Окончательный вывод будет выглядеть примерно так:

Существует множество ресурсов для изучения D3, и я ни в коем случае не являюсь абсолютным экспертом в этом. Я настоятельно рекомендую Видео Куррана Келлехера на YouTube, если вам интересно узнать больше. Вы можете сослаться на полный код моего проекта на GitHub, если вам интересно немного поиграть с кодом. Если вам интересно узнать больше, вы можете посмотреть мою заключительную статью в серии, в которой я рассказываю о картографических данных.

Спасибо, что нашли время, чтобы прочитать это. Ваше здоровье.