我试图了解使用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)
D3中最强大的功能是它能够将数据绑定到DOM元素,它提供了库的名称.通过这样做,您可以通过多种方式基于绑定数据操作这些DOM元素,例如(但不限于):
等等...
如果你没有将数据绑定到DOM元素,例如使用map()
你问题中的方法(与a相同forEach()
),你可以在开头保存几行,但最终你会得到一个尴尬的代码处理后者.让我们来看看它:
这是一个非常简单的代码,使用大部分代码片段,使用以下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")
函数内的所有代码.
之后,我们将比较代码的大小和惯用的"输入","更新"和"退出"选择的大小.
在绑定数据时,显示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行.但是,它会使事情变得非常繁琐.