Итерация функции доступа d3 - PullRequest
0 голосов
/ 23 июня 2018

Это мои данные.

let data = [
    {
      "sample_date": "2017-07-04T00:00:00.000Z",
      "ubiome": [
            {
              "count_norm": 1283,
              "tax_name": "Bacteroides fragilis",
              "tax_rank": "species"
            },
            {
              "count_norm": 3708,
              "tax_name": "Bacteroides thetaiotaomicron",
              "tax_rank": "species"
            },
            {
              "count_norm": 731,
              "tax_name": "Bacteroides uniformis",
              "tax_rank": "species"
            },
            {
              "count_norm": 62226,
              "tax_name": "Bacteroides vulgatus",
              "tax_rank": "species"
            },
            {
              "count_norm": 2139,
              "tax_name": "Parabacteroides distasonis",
              "tax_rank": "species"
            }
      ]
    },
    {
      "sample_date": "2017-07-04T00:00:00.000Z",
      "ubiome": [
        {
          "count_norm": 1283,
          "tax_name": "Bacteroides fragilis",
          "tax_rank": "species"
        },
        {
          "count_norm": 3708,
          "tax_name": "Bacteroides thetaiotaomicron",
          "tax_rank": "species"
        },
        {
          "count_norm": 731,
          "tax_name": "Bacteroides uniformis",
          "tax_rank": "species"
        },
        {
          "count_norm": 62226,
          "tax_name": "Bacteroides vulgatus",
          "tax_rank": "species"
        },
        {
          "count_norm": 2139,
          "tax_name": "Parabacteroides distasonis",
          "tax_rank": "species"
        }
      ]
    }
]

Я пытаюсь использовать d3 для построения тепловой карты. Я знаю, что мне нужно использовать функцию доступа, чтобы использовать примеры дат, а также tax_name и count_norm для построения значений x, y и rect. Я просто не могу заставить функцию доступа пройти первый уровень данных ....

var cells = svg.selectAll('rect')
      .data(data)
      .enter().append('g').append('rect')
      .attr('class', 'cell')
      .attr('width', cellSize)
      .attr('height', cellSize)
      .attr('y', function(d) { return yScale(d.name); })
      .attr('x', function(d) { return xScale(d.rank); })
      .attr('fill', function(d) { return color(d.value); });

Как построить функцию доступа, чтобы получить больше вложенных данных?

Это обновление о том, где я нахожусь. Я не могу заставить x & y pos работать с данными.

let data = [{
    "sample_date": "2017-07-04T00:00:00.000Z",
    "ubiome": [{
        "count_norm": 1283,
        "tax_name": "Bacteroides fragilis",
        "tax_rank": "species"
      },
      {
        "count_norm": 3708,
        "tax_name": "Bacteroides thetaiotaomicron",
        "tax_rank": "species"
      },
      {
        "count_norm": 731,
        "tax_name": "Bacteroides uniformis",
        "tax_rank": "species"
      },
      {
        "count_norm": 62226,
        "tax_name": "Bacteroides vulgatus",
        "tax_rank": "species"
      },
      {
        "count_norm": 2139,
        "tax_name": "Parabacteroides distasonis",
        "tax_rank": "species"
      }
    ]
  },
  {
    "sample_date": "2017-07-10T00:00:00.000Z",
    "ubiome": [{
        "count_norm": 1200,
        "tax_name": "Bacteroides Noway",
        "tax_rank": "species"
      },
      {
        "count_norm": 3700,
        "tax_name": "Bacteroides thetaiotaomicron",
        "tax_rank": "species"
      },
      {
        "count_norm": 700,
        "tax_name": "Bacteroides uniformis",
        "tax_rank": "species"
      },
      {
        "count_norm": 62000,
        "tax_name": "Bacteroides vulgatus",
        "tax_rank": "species"
      },
      {
        "count_norm": 2100,
        "tax_name": "Parabacteroides distasonis",
        "tax_rank": "species"
      }
    ]
  }
];

var dates = [];
var ubiomeonly = [];

var itemSize = 30,
  cellSize = itemSize - 1,
  margin = {
    top: 120,
    right: 20,
    bottom: 20,
    left: 110
  };

var width = 750 - margin.right - margin.left,
  height = 500 - margin.top - margin.bottom;

for (i = 0; i < data.length; i++) {
  var adate = moment(data[i].sample_date).format("YYYY-MM-DD")
  dates.push(adate);
};

var bacteria = [];
for (i = 0; i < data.length; i++) {
  bacteria.push(data[i].ubiome.slice(0, data[i].ubiome.length));
}
var bacteriaList = d3.merge(bacteria).map(function(d) {
  return d.tax_name
});
bacteriaList = d3.set(bacteriaList).values();

var x_elements = dates,
  y_elements = bacteriaList;

var xScale = d3.scaleOrdinal()
  .domain(x_elements)
  .range([0, x_elements.length * itemSize]);

var xAxis = d3.axisTop()
  .scale(xScale)
  .tickFormat(function(d) {
    return d;
  });

var yScale = d3.scaleOrdinal()
  .domain(y_elements)
  .range([0, y_elements.length * itemSize]);

var yAxis = d3.axisLeft()
  .scale(yScale)
  .tickFormat(function(d) {
    return d;
  });

var colorScale = d3.scaleThreshold()
  .domain([0, 10000])
  .range(["#2980B9", "#E67E22", "#27AE60", "#27AE60"]);

var svg = d3.select('#heatmap')
  .data(data)
  .append("svg")
  .attr("width", width + margin.left + margin.right)
  .attr("height", height + margin.top + margin.bottom)
  .append("g")
  .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

var cells = svg.selectAll('rect')
  .data(function(d) {
    return d.ubiome;
  })
  .enter().append('g').append('rect')
  .attr('class', 'cell')
  .attr('width', cellSize)
  .attr('height', cellSize)
  .attr('y', function(d, i) {
    return yScale(d + i);
  })
  .attr('x', function(d) {  return xScale(d.sample_date); })
  .attr('fill', function(d) {
    return colorScale(d.count_norm);
  });

svg.append("g")
  .attr("class", "y axis")
  .call(yAxis)
  .selectAll('text')
  .attr('font-weight', 'normal');

svg.append("g")
  .attr("class", "x axis")
  .call(xAxis)
  .selectAll('text')
  .attr('font-weight', 'normal')
  .style("text-anchor", "start")
  .attr("dx", ".8em")
  .attr("dy", ".5em")
  .attr("transform", function(d) {
    return "rotate(-65)";
  });
<!DOCTYPE html>
<html>

<head>
  <title></title>
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
  <meta charset="utf-8">

</head>

<body>
  <div id="wrapper">
    <h1>uBiome Bacterial Counts</h1>
    <div id="heatmap"></div>
  </div>
  <script src="https://d3js.org/d3.v5.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.22.2/moment.min.js"></script>
</body>

</html>

1 Ответ

0 голосов
/ 23 июня 2018

Вам нужно использовать вложенные выборки.С помощью двух selectAll().data().enter() циклов мы можем добавлять вложенные дочерние элементы в родительские элементы - каждый элемент имеет свои собственные данные.Имея только один selectAll().data().enter(), мы создаем элементы в DOM только для каждого элемента в массиве данных.Каждый элемент в массиве данных может иметь какое-то свойство, которое само является массивом (или самим массивом данных), но вы на самом деле ничего не делаете с дочерними массивами.

Поскольку в вашем массиве только два элемента, будут созданы только два элемента.Поскольку ни один из этих элементов не имеет свойства name или rank, доступ к этим свойствам приведет к неопределенности.

Вот упрощенный пример вашего кода выше, я добавляю p для каждого элемента в массиве данных примера.Каждый p имеет свой текст, установленный как базовый элемент для этого элемента.Вложенные данные все еще являются просто свойством данных каждого p.Поскольку в вашем примере массив данных содержит два элемента, создаются только два элемента:

let data = [{"sample_date":"2017-07-04T00:00:00.000Z","ubiome":[{"count_norm":1283,"tax_name":"Bacteroidesfragilis","tax_rank":"species"},{"count_norm":3708,"tax_name":"Bacteroidesthetaiotaomicron","tax_rank":"species"},{"count_norm":731,"tax_name":"Bacteroidesuniformis","tax_rank":"species"},{"count_norm":62226,"tax_name":"Bacteroidesvulgatus","tax_rank":"species"},{"count_norm":2139,"tax_name":"Parabacteroidesdistasonis","tax_rank":"species"}]},{"sample_date":"2017-07-04T00:00:00.000Z","ubiome":[{"count_norm":1283,"tax_name":"Bacteroidesfragilis","tax_rank":"species"},{"count_norm":3708,"tax_name":"Bacteroidesthetaiotaomicron","tax_rank":"species"},{"count_norm":731,"tax_name":"Bacteroidesuniformis","tax_rank":"species"},{"count_norm":62226,"tax_name":"Bacteroidesvulgatus","tax_rank":"species"},{"count_norm":2139,"tax_name":"Parabacteroidesdistasonis","tax_rank":"species"}]}];

var body = d3.select("body");

body.selectAll("p")
  .data(data)
  .enter()
  .append("p")
  .text(function(d) { return JSON.stringify(d); })
  .style("background-color", function(d,i)  { return ["yellow","skyblue"][i]; })
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>

В вашем примере вы использовали вложенные элементы в своем примере, rect в g, но вы добавляетеодин rect на каждый g (в противном случае вам потребуется использовать другой цикл ввода), и rect использует тот же элемент данных, что и g, это не приведет к созданию элементов с требуемым значением.

Теперь, когда у нас есть два (которые скоро станут родительскими) элемента с датумом, который имеет вложенные данные в качестве некоторого свойства, мы можем вводить новые элементы для каждого родителя.Для этого теперь мы можем сделать цикл selectAll().data().enter() с этими двумя элементами.Помните, что d ниже - это данные для каждого p:

var parents = body.selectAll("p")
 .data(data)
 .enter()
 .append("p");

var children = parents.selectAll("span")
 .data(function(d) { return d.ubiome; }) // d is the parent datum here : {"sample_date":"time","ubiome":[child,child]}
 .enter()
 .append("span")
 .attr("x", function(d) { })  // d is the child datum here

Здесь мы создаем выборку детей для родителей на основе конкретных данных каждого родителя.Теперь мы можем использовать свойство, содержащее информацию для каждого дочернего элемента, получая доступ к родительскому элементу данных.Приведенный ниже фрагмент создает родителя p для каждого родителя снова (снова дифференцируется по цвету), дочерний выбор использует данные родителя и создает span для каждого потомка (дифференцируется по границе).Я установил каждый интервал, чтобы показать его данные.Это дает нам двух родителей p с, каждый с пятью детьми spans.

let data = [{"sample_date":"2017-07-04T00:00:00.000Z","ubiome":[{"count_norm":9876,"tax_name":"Bacteroidesfragilis","tax_rank":"species"},{"count_norm":3708,"tax_name":"Bacteroidesthetaiotaomicron","tax_rank":"species"},{"count_norm":731,"tax_name":"Bacteroidesuniformis","tax_rank":"species"},{"count_norm":62226,"tax_name":"Bacteroidesvulgatus","tax_rank":"species"},{"count_norm":2139,"tax_name":"Parabacteroidesdistasonis","tax_rank":"species"}]},{"sample_date":"2017-07-04T00:00:00.000Z","ubiome":[{"count_norm":1283,"tax_name":"Bacteroidesfragilis","tax_rank":"species"},{"count_norm":3708,"tax_name":"Bacteroidesthetaiotaomicron","tax_rank":"species"},{"count_norm":731,"tax_name":"Bacteroidesuniformis","tax_rank":"species"},{"count_norm":62226,"tax_name":"Bacteroidesvulgatus","tax_rank":"species"},{"count_norm":2139,"tax_name":"Parabacteroidesdistasonis","tax_rank":"species"}]}];

var body = d3.select("body");

var parents = body.selectAll("p")
  .data(data)
  .enter()
  .append("p")
  .style("background-color", function(d,i) { return ["yellow","lightblue"][i]; })

var children = parents.selectAll("span")
  .data(function(d) { return d.ubiome; }) 
  .enter()
  .append("span")
  .text(function(d) { return JSON.stringify(d); })
span { 
  display: block;
  border: 1px dotted black;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>

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


Вот приведенная выше логика, примененная к вашему фрагменту:

let data = [{
    "sample_date": "2017-07-04T00:00:00.000Z",
    "ubiome": [{
        "count_norm": 1283,
        "tax_name": "Bacteroides fragilis",
        "tax_rank": "species"
      },
      {
        "count_norm": 3708,
        "tax_name": "Bacteroides thetaiotaomicron",
        "tax_rank": "species"
      },
      {
        "count_norm": 731,
        "tax_name": "Bacteroides uniformis",
        "tax_rank": "species"
      },
      {
        "count_norm": 62226,
        "tax_name": "Bacteroides vulgatus",
        "tax_rank": "species"
      },
      {
        "count_norm": 2139,
        "tax_name": "Parabacteroides distasonis",
        "tax_rank": "species"
      }
    ]
  },
  {
    "sample_date": "2017-07-10T00:00:00.000Z",
    "ubiome": [{
        "count_norm": 1200,
        "tax_name": "Bacteroides Noway",
        "tax_rank": "species"
      },
      {
        "count_norm": 3700,
        "tax_name": "Bacteroides thetaiotaomicron",
        "tax_rank": "species"
      },
      {
        "count_norm": 700,
        "tax_name": "Bacteroides uniformis",
        "tax_rank": "species"
      },
      {
        "count_norm": 62000,
        "tax_name": "Bacteroides vulgatus",
        "tax_rank": "species"
      },
      {
        "count_norm": 2100,
        "tax_name": "Parabacteroides distasonis",
        "tax_rank": "species"
      }
    ]
  }
];

var dates = [];
var ubiomeonly = [];

var itemSize = 30,
  cellSize = itemSize - 1,
  margin = {
    top: 120,
    right: 20,
    bottom: 20,
    left: 110
  };

var width = 750 - margin.right - margin.left,
  height = 500 - margin.top - margin.bottom;

dates = data.map(function(d) { 
  return d.sample_date;
})


var bacteria = [];
for (i = 0; i < data.length; i++) {
  bacteria.push(data[i].ubiome.slice(0, data[i].ubiome.length));
}
var bacteriaList = d3.merge(bacteria).map(function(d) {
  return d.tax_name
});
bacteriaList = d3.set(bacteriaList).values();

var y_elements = dates,
  x_elements = bacteriaList;

var xScale = d3.scaleBand()
  .domain(x_elements)
  .range([0, x_elements.length * itemSize]);

var xAxis = d3.axisTop()
  .scale(xScale)
  .tickFormat(function(d) {
    return d;
  });
  

var yScale = d3.scaleBand()
  .domain(y_elements)
  .range([0, y_elements.length * itemSize]);

var yAxis = d3.axisLeft()
  .scale(yScale)
  .tickFormat(function(d) {
    return  moment(d).format("YYYY-MM-DD");
  });

var colorScale = d3.scaleThreshold()
  .domain([0, 10000])
  .range(["#2980B9", "#E67E22", "#27AE60", "#27AE60"]);

var svg = d3.select('#heatmap')
  .append("svg")
  .attr("width", width + margin.left + margin.right)
  .attr("height", height + margin.top + margin.bottom)
  .append("g")
  .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

var parents = svg.selectAll(null)
  .data(data)
  .enter().append('g')
  .attr("transform",function(d) { return "translate(0," + yScale(d.sample_date) + ")" });
  
var children = parents.selectAll('rect')
  .data(function(d) { return d.ubiome; })
  .enter()
  .append('rect')
  .attr('class', 'cell')
  .attr('width', cellSize)
  .attr('height', cellSize)
  .attr('x', function(d) {  return xScale(d.tax_name); })
  .attr('fill', function(d) {
    return colorScale(d.count_norm);
  });

svg.append("g")
  .attr("class", "y axis")
  .call(yAxis)
  .selectAll('text')
  .attr('font-weight', 'normal');

svg.append("g")
  .attr("class", "x axis")
  .call(xAxis)
  .selectAll('text')
  .attr('font-weight', 'normal')
  .style("text-anchor", "start")
  .attr("dx", ".8em")
  .attr("dy", ".5em")
  .attr("transform", function(d) {
    return "rotate(-65)";
  });
<script src="https://d3js.org/d3.v5.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.22.2/moment.min.js"></script>
<h1>uBiome Bacterial Counts</h1>
<div id="heatmap"></div>

Что я здесь сделал?

Во-первых, но я не использую вложенные данные, я использую шкалы для ваших дней и видов.(это шкалы, предназначенные для вещей, которые занимают определенную ширину / высоту, такие как гистограммы, они все еще порядковые).Я также не конвертирую вашу строку даты, кроме как для форматирования метки оси, чтобы упростить масштаб. Я также заставил x постоянно ссылаться на горизонтальные шкалы / данные / оси и то же самое с y (следовательно, почему ваши оси перевернуты, я также не привязываю данные к svg, так как это не нужно) .

Во-вторых, я создал группу для каждого периода выборки (родительский элемент).Я применяю перевод к этим родительским g s на основе масштабированного значения даты (содержится в исходных данных).Делая это, я рассматриваю каждого родителя как строку (я перевожу только значение y). Хотя мне не нужно было ничего размещать в текстовых примерах выше, я применил цвет фона .

В-третьих, я создаю дочерние элементы для каждого родителя, они расположены в зависимости от вида (так как строка уже размещена с переводом на родителя g). Доступ к виду осуществляется из исходных данных ребенка (прямоугольник) - элемента в одном из массивов второго уровня в исходном наборе данных. Чтобы установить данные каждого ребенка, я использую данные родителя (d.ubiome - не весь элемент данных) в цикле selectAll().data().enter(), как в приведенном выше примере с текстом.

В этом шаблоне каждый родительский элемент данных является элементом в исходном массиве. Каждый дочерний элемент данных является элементом в некотором массиве, который содержится в элементах данных соответствующего дочернего элемента. Следовательно, почему мы используем parent.selectAll().data(function(d) { return d.ubiome }).enter()

Вот и все. Я не обращаюсь ни к каким родительским данным для дочерних элементов, так как строки располагают дочерние элементы, но если вам нужно было получить доступ к родительским данным, вы можете использовать несколько подходов, один из них - выбрать родительский элемент из функции доступа: d3.select(this.parentElement).data() или использовать локальную переменную.


Вы можете спросить, почему d3 не связывает вложенные данные с вложенными элементами, что, по-видимому, соответствует ожидаемому вами поведению. Чаще всего вложенные элементы имеют тот же элемент данных, что и их родительские элементы, поскольку это облегчает такие вещи, как маркировка (круг и текст в родительском элементе g). Данные со свойствами, которые содержат массивы, могут разрешать множественные представления данных (анимация во времени, различные классификации или представления данных и т. Д.), И, таким образом, массивы не являются вложенными данными, которые когда-либо будут представлены своими собственными элементами. И иногда наборы данных будут иметь несколько свойств, которые содержат массивы (которые сами содержат массивы, например, geojson), как это поведение будет знать, какой массив использовать для дочерних элементов?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...