Array.map()vs d3.selectAll()。data.enter()

时间:2017-06-04 13:56:55

标签: javascript d3.js

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

  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()循环遍历数据集一样,我可以使用更清晰的代码实现相同的功能。更少的线条,特别是当我创建一个更复杂的可视化而不是像这样的简单准系统条形图时。

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

1 个答案:

答案 0 :(得分:8)

D3代表数据驱动文档

D3中最强大的功能是它能够将数据绑定到DOM元素,它提供了库的名称。通过这样做,您可以通过多种方式基于绑定数据操作这些DOM元素,例如(但不限于):

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

等等......

如果您没有将数据绑定到DOM元素,例如在您的问题中使用map()方法(与forEach()相同),则可以节省几个在开头的行,但你会得到一个尴尬的代码来处理后者。让我们看看:

map()方法

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



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>
&#13;
&#13;
&#13;

这似乎是一个很好的结果,酒吧都在那里。但是,无数据绑定到这些矩形。保留此代码,我们将在下面的挑战中使用它。

输入选择

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

&#13;
&#13;
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);
&#13;
<script src="https://d3js.org/d3.v4.min.js"></script>
&#13;
&#13;
&#13;

正如您所看到的,它比之前的map()方法稍微长一点,长了2行。

然而,这实际上将数据绑定到那些矩形。如果你在console.log中选择其中一个矩形的D3,你会看到类似这样的内容(在Chrome中):

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

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

挑战

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

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

&#13;
&#13;
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);

g1.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 = g2.append("g").attr("transform", "translate(" + p + ",0)")
  .call(axis);

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

  var thisValue = this.id;

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

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

  var rects = g1.selectAll("rect")
    .data(newData, 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);
})
&#13;
<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>
&#13;
&#13;
&#13;

清洁代码以某种方式基于意见,但我们可以轻松地衡量尺寸。

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

之后,我们会比较代码的大小和惯用语的大小&#34;输入&#34;,&#34;更新&#34;和&#34;退出&#34;选择。

Chalenge#2

在绑定数据方面,显示D3功能时,这个挑战2号可能更有趣。

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

这里的好处是关键功能,它将每个条与每个数据点相关联,在本例中为name属性:

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

以下是代码,请等待1秒后再点击&#34;更新&#34;:

&#13;
&#13;
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);
})
&#13;
<script src="https://d3js.org/d3.v4.min.js"></script>
<button>Update</button>
<br>
<br>
&#13;
&#13;
&#13;

您的挑战是相同的:更改.on("click")内的代码,这就是这个......

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()方法。

请注意,由于我对条形图进行了排序,因此您无法再通过数据数组的索引更改这些条形图!

结论

第一次绘制元素时,map()方法可以为您节省2行。但是,它会使事情变得非常繁琐。