d3访问器函数迭代

时间:2018-06-23 19:31:03

标签: javascript object d3.js multidimensional-array

这是我的数据。

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 :(得分:1)

您需要使用嵌套选择。通过两个selectAll().data().enter()循环,我们可以在父元素中附加嵌套的子元素-每个元素都有自己的基准。仅使用一个selectAll().data().enter(),我们仅在DOM中为数据数组中的每个项目创建元素。数据数组中的每个项目都可能具有某些属性,该属性本身就是一个数组(或者本身就是一个数据数组)-但是您实际上尚未对子数组进行任何操作。

由于数组中只有两个项目,因此只会创建两个元素。由于这些项目都不具有namerank属性,因此访问这些属性将导致未定义。

这是上面代码的简化示例,我为示例数据数组中的每个项目附加了一个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,但是您要向每个{{1 }}(否则,您将需要使用另一个输入循环),并且rectg共享相同的基准,这将不会创建具有所需基准的元素。

现在,我们有两个(即将成为父级)元素,其基准面具有嵌套数据作为某些属性,我们可以为每个父级输入新元素。为此,我们现在可以对这两个元素进行一个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; }的特定属性,但是您可以看到跨度的数据对应于父项的ubiome数组的项。因此,访问子级的特定属性应该非常简单。


以下是适用于您的代码段的逻辑:

<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>
d

我在这里做什么?

首先,但与嵌套数据无关,我在天和物种上都使用了带刻度(这些刻度是为占用特定宽度/高度的东西(如条形图而设计的,但仍然是顺序的)。除了格式化轴标签以简化比例之外,我也不会转换您的日期字符串。 我还使x始终引用水平比例尺/数据/轴,并与y相同(因此为什么轴被翻转,我也不会将数据绑定到svg上,因为这是不必要的)。 / p>

第二,我为每个采样周期(父元素)创建了一个组。我根据日期的缩放值(包含在父基准中)对这些父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)"; });应用了转换。通过这样做,我将每个父级都视为一行(我仅转换y值)。 虽然我不必在上面的文本示例中放置任何内容,但我改用了背景色

第三,我正在为每个父级创建子级元素,这些子级元素是根据种类进行定位的(因为该行已经在父级<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>上进行了平移)。从子(矩形)的基准面访问该物种-原始数据集中第二级数组之一中的一项。要设置每个孩子的基准,我在嵌套的g循环中使用父级的基准(d.ubiome-而不是整个基准),就像上面带有文本的示例一样。

在此模式中,每个父基准都是原始数组中的一个项目。每个子基准都是该子各自父基准中包含的数组中的一项。因此,为什么我们使用g

就是这样。当行将子级放置在子级上时,我没有访问任何子级父级数据,但是如果您需要访问父级数据,则可以使用几种方法,一种是从访问器函数中选择父级:selectAll().data().enter() ,或使用局部变量。


您可能会问,为什么d3不将嵌套的数据绑定到嵌套的元素,这似乎是您期望的行为。嵌套元素经常与它们的父元素共享相同的数据,因为这样便于进行标注(父g中的圆圈和文字)。具有包含数组的属性的数据可能允许数据的多种表示形式(通过时间进行动画处理,数据的不同分类或表示形式等),因此,数组不是嵌套的数据,它们将永远使用其自己的元素来表示。有时数据集将具有多个包含数组的属性(它们本身包含数组,例如:geojson),这种行为如何知道要为子级使用哪个数组?