Array.map() 与 d3.selectAll().data.enter() [英] Array.map() vs d3.selectAll().data.enter()

查看:15
本文介绍了Array.map() 与 d3.selectAll().data.enter()的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我试图了解使用 d3.selectAll.data.enter() 循环遍历数据集并绘制它的好处.

I am trying to understand what's benefit of using d3.selectAll.data.enter() to loop through a dataset and plot it.

  var data = [4, 8, 15, 16, 23, 42];

  var x = d3.scale.linear()
      .domain([0, d3.max(data)])
      .range([0, 420]);

  let chartsvg = d3.select(".chart").append("svg");

  chartsvg.selectAll("rect")
    .data(data)
    .enter()
    .append("rect")
    .attr("x", 0)
    .attr("y", function(d, i) {
    return 25*i;
  })
    .attr("width", function(d) {
    return x(d);
  })
    .attr("height", 20)
    .attr("fill", "#f3b562");

我看到了 d3 功能的很多好处,例如比例尺、轴等.但感觉就像使用 Array.map() 循环遍历数据集,我可以用更简洁的代码和更少的行来实现相同的功能,尤其是当我创建一个更复杂的可视化而不是像这样的简单准系统条形图时.

I see a lot of benefit of d3's functionalities like scale, axes, etc. But it feels like using Array.map() for looping through the dataset, I can achieve the same functionality with much cleaner code and fewer lines, especially when I am creating a much more complex visualization and not a simple barebones bar chart like this.

  var data = [4, 8, 15, 16, 23, 42];

  var x = d3.scale.linear()
      .domain([0, d3.max(data)])
      .range([0, 420]);

  let chartsvg = d3.select(".chart").append("svg");

  data.map(function(d, i){
    chartsvg.append("rect")
      .attr("x", 0)
      .attr("y", 25*i)  
      .attr("width", x(d))
      .attr("height", 20)
      .attr("fill", "#f3b562");
  });

推荐答案

D3 代表数据驱动文档

D3 中最强大的特性,也就是库的名字,就是它能够将数据绑定到 DOM 元素.通过这样做,您可以通过多种方式基于绑定数据操作那些 DOM 元素,例如(但不限于):

D3 stands for Data-Driven Documents

The most powerful feature in D3, which gives the very name of the library, is its ability to bind data to DOM elements. By doing this, you can manipulate those DOM elements based on the bound data in several ways, such as (but not limited to):

  • 排序
  • 过滤器
  • 翻译
  • 风格
  • 附加
  • 删除

等等……

如果您不将数据绑定到 DOM 元素,例如在您的问题中使用 map() 方法(这与 forEach()),你可以在开始时节省几行,但你最终会得到一个笨拙的代码来处理.我们来看看:

If you don't bind data to the DOM elements, for instance using the map() approach in your question (which is the same of a forEach()), you may save a couple of lines at the beginning, but you will end up with an awkward code to deal with later. Let's see it:

这是一个非常简单的代码,使用您的大部分代码段,使用 map() 方法创建条形图:

Here is a very simple code, using most of your snippet, to create a bar chart using the map() approach:

var h = 250,
  w = 500,
  p = 40;
var svg = d3.select("body")
  .append("svg")
  .attr("width", w)
  .attr("height", h);

var data = [{
  group: "foo",
  value: 14,
  name: "A"
}, {
  group: "foo",
  value: 35,
  name: "B"
}, {
  group: "foo",
  value: 87,
  name: "C"
}, {
  group: "foo",
  value: 12,
  name: "D"
}, {
  group: "bar",
  value: 84,
  name: "E"
}, {
  group: "bar",
  value: 65,
  name: "F"
}, {
  group: "bar",
  value: 34,
  name: "G"
}, {
  group: "baz",
  value: 98,
  name: "H"
}, {
  group: "baz",
  value: 12,
  name: "I"
}, {
  group: "baz",
  value: 43,
  name: "J"
}, {
  group: "baz",
  value: 66,
  name: "K"
}, {
  group: "baz",
  value: 42,
  name: "L"
}];


var color = d3.scaleOrdinal(d3.schemeCategory10);

var xScale = d3.scaleLinear()
  .range([0, w - p])
  .domain([0, d3.max(data, function(d) {
    return d.value
  })]);

var yScale = d3.scaleBand()
  .range([0, h])
  .domain(data.map(function(d) {
    return d.name
  }))
  .padding(0.1);

data.map(function(d, i) {
  svg.append("rect")
    .attr("x", p)
    .attr("y", yScale(d.name))
    .attr("width", xScale(d.value))
    .attr("height", yScale.bandwidth())
    .attr("fill", color(d.group));
});

var axis = d3.axisLeft(yScale);
var gY = svg.append("g").attr("transform", "translate(" + p + ",0)")
  .call(axis);

<script src="https://d3js.org/d3.v4.min.js"></script>

这似乎是一个不错的结果,条形都在那里.但是,这些矩形没有数据绑定.保留此代码,我们将在下面的挑战中使用它.

It seems to be a nice result, the bars are all there. However, there is no data bound to those rectangles. Keep this code, we'll use it in the challenge below.

现在让我们尝试相同的代码,但使用惯用的输入"选择:

Now let's try the same code, but using the idiomatic "enter" selection:

var h = 250,
  w = 500,
  p = 40;
  
var svg = d3.select("body")
  .append("svg")
  .attr("width", w)
  .attr("height", h);

var data = [{
  group: "foo",
  value: 14,
  name: "A"
}, {
  group: "foo",
  value: 35,
  name: "B"
}, {
  group: "foo",
  value: 87,
  name: "C"
}, {
  group: "foo",
  value: 12,
  name: "D"
}, {
  group: "bar",
  value: 84,
  name: "E"
}, {
  group: "bar",
  value: 65,
  name: "F"
}, {
  group: "bar",
  value: 34,
  name: "G"
}, {
  group: "baz",
  value: 98,
  name: "H"
}, {
  group: "baz",
  value: 12,
  name: "I"
}, {
  group: "baz",
  value: 43,
  name: "J"
}, {
  group: "baz",
  value: 66,
  name: "K"
}, {
  group: "baz",
  value: 42,
  name: "L"
}];


var color = d3.scaleOrdinal(d3.schemeCategory10);

var xScale = d3.scaleLinear()
  .range([0, w - p])
  .domain([0, d3.max(data, function(d) {
    return d.value
  })]);

var yScale = d3.scaleBand()
  .range([0, h])
  .domain(data.map(function(d) {
    return d.name
  }))
  .padding(0.1);

svg.selectAll(null)
  .data(data, function(d) {
    return d.name
  })
  .enter()
  .append("rect")
  .attr("x", p)
  .attr("y", function(d) {
    return yScale(d.name)
  })
  .attr("width", function(d) {
    return xScale(d.value)
  })
  .attr("height", yScale.bandwidth())
  .attr("fill", function(d) {
    return color(d.group)
  });

var axis = d3.axisLeft(yScale);
var gY = svg.append("g").attr("transform", "translate(" + p + ",0)")
  .call(axis);

<script src="https://d3js.org/d3.v4.min.js"></script>

如你所见,它比之前的map()方法稍微长了一点,多了2行.

As you can see, it's a little longer than the previous map() method, 2 lines longer.

然而,这实际上将数据绑定到这些矩形.如果您在控制台中记录这些矩形之一的 D3 选择,您将看到如下内容(在 Chrome 中):

However, this actually binds data to those rectangles. If you console.log a D3 selection of one of those rectangles, you'll see something like this (in Chrome):

> Selection
  > _groups: Array(1)
    > 0: Array(1)
      > 0: rect
        > __data__: Object
          group: "bar"
          name: "G"
          value: 34

由于此代码实际上将数据绑定到 DOM 元素,因此您可以使用 map() 方法以一种麻烦的方式(至少可以说)操作它们.我将在下一个片段中展示这一点,该片段将用于提出挑战.

Since this code actually binds data to the DOM elements, you can manipulate them in a way that would be cumbersome (to say the least) using the map() approach. I'll show this in the next snippet, which will be used to propose a challenge.

由于您的问题涉及更简洁的代码和更少的行,因此这对您来说是一个挑战.

Since your question talks about cleaner code and fewer lines, here is a challenge for you.

我创建了 3 个按钮,一个用于 data 数组中的每个组(第四个用于所有组).当您单击该按钮时,它会过滤数据并相应地更新图表:

I created 3 buttons, one for each group in the data array (and a fourth one for all the groups). When you click the button, it filters the data and updates the chart accordingly:

var h = 250,
  w = 500,
  p = 40;
var svg = d3.select("body")
  .append("svg")
  .attr("width", w)
  .attr("height", h);

var g1 = svg.append("g")
var g2 = svg.append("g")

var data = [{
  group: "foo",
  value: 14,
  name: "A"
}, {
  group: "foo",
  value: 35,
  name: "B"
}, {
  group: "foo",
  value: 87,
  name: "C"
}, {
  group: "foo",
  value: 12,
  name: "D"
}, {
  group: "bar",
  value: 84,
  name: "E"
}, {
  group: "bar",
  value: 65,
  name: "F"
}, {
  group: "bar",
  value: 34,
  name: "G"
}, {
  group: "baz",
  value: 98,
  name: "H"
}, {
  group: "baz",
  value: 12,
  name: "I"
}, {
  group: "baz",
  value: 43,
  name: "J"
}, {
  group: "baz",
  value: 66,
  name: "K"
}, {
  group: "baz",
  value: 42,
  name: "L"
}];


var color = d3.scaleOrdinal(d3.schemeCategory10);

var xScale = d3.scaleLinear()
  .range([0, w - p])
  .domain([0, d3.max(data, function(d) {
    return d.value
  })]);

var yScale = d3.scaleBand()
  .range([0, h])
  .domain(data.map(function(d) {
    return d.name
  }))
  .padding(0.1);


var axis = d3.axisLeft(yScale);
var gY = g2.append("g").attr("transform", "translate(" + p + ",0)")
  .call(axis);

draw(data);

function draw(data) {

  yScale.domain(data.map(function(d) {
    return d.name
  }))

  var rects = g1.selectAll("rect")
    .data(data, function(d) {
      return d.name
    })

  rects.enter()
    .append("rect")
    .attr("x", p)
    .attr("y", function(d) {
      return yScale(d.name)
    })
    .attr("width", 0)
    .attr("height", yScale.bandwidth())
    .attr("fill", function(d) {
      return color(d.group)
    })
    .transition()
    .duration(1000)
    .attr("width", function(d) {
      return xScale(d.value)
    });

  rects.transition()
    .duration(1000)
    .attr("x", p)
    .attr("y", function(d) {
      return yScale(d.name)
    })
    .attr("width", function(d) {
      return xScale(d.value)
    })
    .attr("height", yScale.bandwidth())
    .attr("fill", function(d) {
      return color(d.group)
    });

  rects.exit()
    .transition()
    .duration(1000)
    .attr("width", 0)
    .remove();

  gY.transition().duration(1000).call(axis);
};

d3.selectAll("button").on("click", function() {

  var thisValue = this.id;

  var newData = thisValue === "all" ? data : data.filter(function(d) {
    return d.group === thisValue;
  });

  draw(newData)
});

<script src="https://d3js.org/d3.v4.min.js"></script>
<button id="foo">Foo</button>
<button id="bar">Bar</button>
<button id="baz">Baz</button>
<button id="all">All</button>
<br>
<br>

更简洁的代码在某种程度上基于意见,但我们可以轻松衡量大小.

A cleaner code is somehow opinion-based, but we can easily measure size.

因此,这里的挑战是:尝试创建一个执行相同操作的代码,但使用 map() 方法,即不绑定任何数据.做我在这里做的所有转换.您将尝试重新创建的代码是 on("click") 函数中的所有代码.

Thus, here is the challenge: try to create a code that does the same, but using the map() approach, that is, without binding any data. Do all the transitions I'm doing here. The code you will try to recreate is all the code inside the on("click") function.

之后,我们将比较您的代码的大小和惯用的输入"、更新"和更新"的大小.和退出"选择.

After that, we'll compare the size of your code and the size of an idiomatic "enter", "update" and "exit" selections.

在绑定数据方面,展示 D3 功能的第二个挑战可能更有趣.

This challenge number 2 may be even more interesting to show D3 capabilities when it comes to binding data.

在这个新代码中,我在 1 秒后对原始数据数组进行排序,并重新绘制图表.然后,点击更新"按钮,我将另一个数据数组绑定到条形.

In this new code, I'm sorting the original data array after 1 second, and redrawing the chart. Then, clicking on the "update" button, I'm binding another data array to the bars.

这里的好处是关键函数,它使用 name 属性将每个条形与每个数据点相关联:

The nice thing here is the key function, that associates each bar to each data point using, in this case, the name property:

.data(data, function(d) {
    return d.name
})

这是代码,请等待 1 秒再点击更新":

Here is the code, please wait 1 second before clicking "update":

var h = 250,
  w = 500,
  p = 40;

var svg = d3.select("body")
  .append("svg")
  .attr("width", w)
  .attr("height", h);

var data2 = [{
  group: "foo",
  value: 10,
  name: "A"
}, {
  group: "foo",
  value: 20,
  name: "B"
}, {
  group: "foo",
  value: 30,
  name: "C"
}, {
  group: "foo",
  value: 40,
  name: "D"
}, {
  group: "bar",
  value: 50,
  name: "E"
}, {
  group: "bar",
  value: 60,
  name: "F"
}, {
  group: "bar",
  value: 70,
  name: "G"
}, {
  group: "baz",
  value: 80,
  name: "H"
}, {
  group: "baz",
  value: 85,
  name: "I"
}, {
  group: "baz",
  value: 90,
  name: "J"
}, {
  group: "baz",
  value: 95,
  name: "K"
}, {
  group: "baz",
  value: 100,
  name: "L"
}];

var data = [{
  group: "foo",
  value: 14,
  name: "A"
}, {
  group: "foo",
  value: 35,
  name: "B"
}, {
  group: "foo",
  value: 87,
  name: "C"
}, {
  group: "foo",
  value: 12,
  name: "D"
}, {
  group: "bar",
  value: 84,
  name: "E"
}, {
  group: "bar",
  value: 65,
  name: "F"
}, {
  group: "bar",
  value: 34,
  name: "G"
}, {
  group: "baz",
  value: 98,
  name: "H"
}, {
  group: "baz",
  value: 12,
  name: "I"
}, {
  group: "baz",
  value: 43,
  name: "J"
}, {
  group: "baz",
  value: 66,
  name: "K"
}, {
  group: "baz",
  value: 42,
  name: "L"
}];

var color = d3.scaleOrdinal(d3.schemeCategory10);

var xScale = d3.scaleLinear()
  .range([0, w - p])
  .domain([0, d3.max(data, function(d) {
    return d.value
  })]);

var yScale = d3.scaleBand()
  .range([0, h])
  .domain(data.map(function(d) {
    return d.name
  }))
  .padding(0.1);

svg.selectAll(".bars")
  .data(data, function(d) {
    return d.name
  })
  .enter()
  .append("rect")
  .attr("class", "bars")
  .attr("x", p)
  .attr("y", function(d) {
    return yScale(d.name)
  })
  .attr("width", function(d) {
    return xScale(d.value)
  })
  .attr("height", yScale.bandwidth())
  .attr("fill", function(d) {
    return color(d.group)
  })

var axis = d3.axisLeft(yScale);
var gY = svg.append("g").attr("transform", "translate(" + p + ",0)")
  .call(axis);

setTimeout(function() {

  data.sort(function(a, b) {
    return d3.ascending(a.value, b.value)
  });

  yScale.domain(data.map(function(d) {
    return d.name
  }));

  svg.selectAll(".bars").data(data, function(d) {
      return d.name
    })
    .transition()
    .duration(500)
    .attr("y", function(d) {
      return yScale(d.name)
    })
    .attr("width", function(d) {
      return xScale(d.value)
    });

  gY.transition().duration(1000).call(axis);

}, 1000)

d3.selectAll("button").on("click", function() {

  svg.selectAll(".bars").data(data2, function(d) {
      return d.name
    })
    .transition()
    .duration(500)
    .attr("y", function(d) {
      return yScale(d.name)
    })
    .attr("width", function(d) {
      return xScale(d.value)
    });

  gY.transition().duration(1000).call(axis);
})

<script src="https://d3js.org/d3.v4.min.js"></script>
<button>Update</button>
<br>
<br>

您在这里的挑战是相同的:更改 .on("click") 中的代码,就是这样...

Your challenge here is the same: change the code inside .on("click"), which is just this...

svg.selectAll(".bars").data(data2, function(d) {
        return d.name
    })
    .transition()
    .duration(500)
    .attr("y", function(d) {
        return yScale(d.name)
    })
    .attr("width", function(d) {
        return xScale(d.value)
    });

gY.transition().duration(1000).call(axis);

... 用于相同的代码,但用于您的 map() 方法.

... to a code that does the same, but for your map() approach.

请记住,由于我对条形进行了排序,因此您不能再通过数据数组的索引来更改它们!

Bear in mind that, since I sorted the bars, you cannot change them by the index of your data array anymore!

当您第一次绘制元素时,map() 方法可能会为您节省 2 行.但是,这会使以后的事情变得非常麻烦.

The map() approach may save you 2 lines when you first draw the elements. However, it will make things terribly cumbersome later on.

这篇关于Array.map() 与 d3.selectAll().data.enter()的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆