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

use*_*637 5 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");
Run Code Online (Sandbox Code Playgroud)

我看到d3的功能有很多好处,比如scale,axes等.但感觉就像使用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");
  });
Run Code Online (Sandbox Code Playgroud)

Ger*_*ado 9

D3代表数据驱动文档

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

  • 分类
  • 过滤
  • 翻译
  • 样式
  • 附加
  • 去掉

等等...

如果你没有将数据绑定到DOM元素,例如使用map()你问题中的方法(与a相同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);
Run Code Online (Sandbox Code Playgroud)
<script src="https://d3js.org/d3.v4.min.js"></script>
Run Code Online (Sandbox Code Playgroud)

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

输入选择

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

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);
Run Code Online (Sandbox Code Playgroud)
<script src="https://d3js.org/d3.v4.min.js"></script>
Run Code Online (Sandbox Code Playgroud)

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

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

> Selection
  > _groups: Array(1)
    > 0: Array(1)
      > 0: rect
        > __data__: Object
          group: "bar"
          name: "G"
          value: 34
Run Code Online (Sandbox Code Playgroud)

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

挑战

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

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

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)
});
Run Code Online (Sandbox Code Playgroud)
<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>
Run Code Online (Sandbox Code Playgroud)

一个更干净的代码是基于某种方式舆论,但我们可以很容易地测量尺寸.

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

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

Chalenge#2

在绑定数据时,显示D3功能可能会更加有趣.

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

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

.data(data, function(d) {
    return d.name
})
Run Code Online (Sandbox Code Playgroud)

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

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);
})
Run Code Online (Sandbox Code Playgroud)
<script src="https://d3js.org/d3.v4.min.js"></script>
<button>Update</button>
<br>
<br>
Run Code Online (Sandbox Code Playgroud)

你在这里遇到的挑战是一样的:改变里面的代码.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);
Run Code Online (Sandbox Code Playgroud)

...对于执行相同操作的代码,但对于您的map()方法.

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

结论

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