用ES6绘制饼图
Drawing Pie chart with ES6
我尝试基于 D3.js 和具有动画和交互性的 ES6 创建自己的图表库。
我的问题是绘制饼图需要一些补间函数才能很好地动画饼图。我尝试用 ES6 编写那些补间函数。
我的图表结构如下所示:
class PieChart {
constructor({w, h} = {}) {
this.w = w;
this.h = h;
...
this.onInit();
}
onInit() {
this.radius = Math.min(this.w, this.h) / 2;
this.arc = d3.arc()
.innerRadius(this.radius - 20)
.outerRadius(this.radius);
this.pie = d3.pie();
...
this.svg = d3.select("#id")
.append("svg")
.attr("width", this.w)
.attr("height", this.h)
this.drawChart();
}
drawChart() {
this.arcs = this.svg.append("g")
.attr("transform", `translate(${this.w / 2}, ${this.h / 2})`)
.attr("class", "slices")
.selectAll(".arc")
.data(this.dataset)
.enter()
.append("path")
.attr("d", this.arc)
.each(function(d) { this._current = d; });
...
const curryAttrTween = function() {
let outerArc = this.arc;
let radius = this.radius;
return function(d) { // <- PROBLEM: This inner function is never called
this._current = this._current || d;
let interpolate = d3.interpolate(this._current, d);
this._current = interpolate(0);
return function(t) {
let d2 = interpolate(t);
let pos = outerArc.centroid(d2);
pos[0] = radius * (midAngle(d2) < Math.PI ? 1 : -1);
return `translate(${pos})`;
}
}
};
let labels = this.svg.select(".label-name").selectAll("text")
.data(this.pie(this.dataset), "key");
labels
.enter()
.append("text")
.attr("dy", ".35em")
.attr("class", "text")
.text((d) => `${d.data.column}: ${d.data.data.count}`);
labels
.transition()
.duration(666)
.attrTween("d", curryAttrTween.bind(this)());
labels
.exit()
.remove();
}
}
我也试过:
drawChart() {
...
const attrTween = function(d) {
this._current = this._current || d; // <- PROBLEM: Can't access scope 'this'
let interpolate = d3.interpolate(this._current, d);
this._current = interpolate(0);
return function(t) {
let d2 = interpolate(t);
let pos = this.arc.centroid(d2);
pos[0] = this.radius * (midAngle(d2) < Math.PI ? 1 : -1);
return `translate(${pos})`;
}
}
labels
.transition()
.duration(666)
.attrTween("d", (d) => attrTween(d));
...
}
我终于尝试了:
drawChart() {
...
labels
.transition()
.duration(666)
.attrTween("d", function(d) {
this._current = this._current || d;
let interpolate = d3.interpolate(this._current, d);
this._current = interpolate(0);
return function(t) {
let d2 = interpolate(t);
let pos = this.arc.centroid(d2); // <- PROBLEM: Can't access this.arc
pos[0] = this.radius * (midAngle(d2) < Math.PI ? 1 : -1); // <- PROBLEM: Can't access this.radius
return `translate(${pos})`;
}
});
...
}
以上所有方法都在某个时候失败了。我指出了我的代码中的问题,我不确定在 ES6 中是否可以以及如何做到这一点。
我只想 post 解决方案,这是我在@RyanMorton 的帮助下找到的,供将来自己使用。
解决方法是定义变量const self = this;
。这样我们就可以在匿名函数中访问ES6的this
。
drawChart() {
...
const self = this;
labels
.transition()
.duration(666)
.attrTween("d", function(d) {
this._current = this._current || d;
let interpolate = d3.interpolate(this._current, d);
this._current = interpolate(0);
return function(t) {
let d2 = interpolate(t);
let pos = self.arc.centroid(d2);
pos[0] = self.radius * (midAngle(d2) < Math.PI ? 1 : -1);
return `translate(${pos})`;
}
});
...
}
虽然您的 有效,但它是 pre-ES6 解决方案的示例,您在其中使用闭包 const self = this;
来捕获外部 this
范围。这在某种程度上感觉像是在回避您自己的问题,该问题要求 ES6 解决方案。
此方法的 ES6 替代方案是使用 arrow function。箭头函数的一个好处是,它们从定义它们的封闭范围(词法)中选择它们的 this
,而传统函数有它们自己的 this
阻止您访问外部范围。这使得箭头函数作为 OOP 中使用的回调特别有用,您希望从回调中访问实例的属性。
可以很容易地重写您的代码以利用此功能:
drawChart() {
//...
// Use arrow function to lexically capture this scope.
const interpolator = t => {
let d2 = interpolate(t);
let pos = this.arc.centroid(d2);
pos[0] = this.radius * (midAngle(d2) < Math.PI ? 1 : -1);
return `translate(${pos})`;
};
labels
.transition()
.duration(666)
.attrTween("d", function(d) {
this._current = this._current || d;
let interpolate = d3.interpolate(this._current, d);
this._current = interpolate(0);
return interpolator;
});
//...
}
注意,这仍然使用普通函数作为提供给 .attrTween()
的 插值器工厂 回调,因为此函数依赖于 this
绑定到迭代的当前 DOM 元素而不是外部范围。
进一步阅读:Chapter 13. Arrow Functions from the excellent book Exploring ES6 Axel Rauschmayer 博士。
由于我无法对已接受的发表评论(没有足够的声誉:()我只想指出新创建的内部函数(interpolator ) 将无法访问在 attrTween:
中声明的 interpolate
drawChart() {
//...
// Use arrow function to lexically capture this scope.
const interpolator = t => {
let d2 = interpolate(t); // <-- Reference Error
let pos = this.arc.centroid(d2);
pos[0] = this.radius * (midAngle(d2) < Math.PI ? 1 : -1);
return `translate(${pos})`;
};
labels
.transition()
.duration(666)
.attrTween("d", function(d) {
this._current = this._current || d;
let interpolate = d3.interpolate(this._current, d); // <-- Declared here
this._current = interpolate(0);
return interpolator;
});
//...
}
ps. 我发现:
const self = this;
在这种情况下是一个很好的解决方案,因为它更容易阅读和推理,即使它不是最好的 ES6 方式。
我尝试基于 D3.js 和具有动画和交互性的 ES6 创建自己的图表库。
我的问题是绘制饼图需要一些补间函数才能很好地动画饼图。我尝试用 ES6 编写那些补间函数。
我的图表结构如下所示:
class PieChart {
constructor({w, h} = {}) {
this.w = w;
this.h = h;
...
this.onInit();
}
onInit() {
this.radius = Math.min(this.w, this.h) / 2;
this.arc = d3.arc()
.innerRadius(this.radius - 20)
.outerRadius(this.radius);
this.pie = d3.pie();
...
this.svg = d3.select("#id")
.append("svg")
.attr("width", this.w)
.attr("height", this.h)
this.drawChart();
}
drawChart() {
this.arcs = this.svg.append("g")
.attr("transform", `translate(${this.w / 2}, ${this.h / 2})`)
.attr("class", "slices")
.selectAll(".arc")
.data(this.dataset)
.enter()
.append("path")
.attr("d", this.arc)
.each(function(d) { this._current = d; });
...
const curryAttrTween = function() {
let outerArc = this.arc;
let radius = this.radius;
return function(d) { // <- PROBLEM: This inner function is never called
this._current = this._current || d;
let interpolate = d3.interpolate(this._current, d);
this._current = interpolate(0);
return function(t) {
let d2 = interpolate(t);
let pos = outerArc.centroid(d2);
pos[0] = radius * (midAngle(d2) < Math.PI ? 1 : -1);
return `translate(${pos})`;
}
}
};
let labels = this.svg.select(".label-name").selectAll("text")
.data(this.pie(this.dataset), "key");
labels
.enter()
.append("text")
.attr("dy", ".35em")
.attr("class", "text")
.text((d) => `${d.data.column}: ${d.data.data.count}`);
labels
.transition()
.duration(666)
.attrTween("d", curryAttrTween.bind(this)());
labels
.exit()
.remove();
}
}
我也试过:
drawChart() {
...
const attrTween = function(d) {
this._current = this._current || d; // <- PROBLEM: Can't access scope 'this'
let interpolate = d3.interpolate(this._current, d);
this._current = interpolate(0);
return function(t) {
let d2 = interpolate(t);
let pos = this.arc.centroid(d2);
pos[0] = this.radius * (midAngle(d2) < Math.PI ? 1 : -1);
return `translate(${pos})`;
}
}
labels
.transition()
.duration(666)
.attrTween("d", (d) => attrTween(d));
...
}
我终于尝试了:
drawChart() {
...
labels
.transition()
.duration(666)
.attrTween("d", function(d) {
this._current = this._current || d;
let interpolate = d3.interpolate(this._current, d);
this._current = interpolate(0);
return function(t) {
let d2 = interpolate(t);
let pos = this.arc.centroid(d2); // <- PROBLEM: Can't access this.arc
pos[0] = this.radius * (midAngle(d2) < Math.PI ? 1 : -1); // <- PROBLEM: Can't access this.radius
return `translate(${pos})`;
}
});
...
}
以上所有方法都在某个时候失败了。我指出了我的代码中的问题,我不确定在 ES6 中是否可以以及如何做到这一点。
我只想 post 解决方案,这是我在@RyanMorton 的帮助下找到的,供将来自己使用。
解决方法是定义变量const self = this;
。这样我们就可以在匿名函数中访问ES6的this
。
drawChart() {
...
const self = this;
labels
.transition()
.duration(666)
.attrTween("d", function(d) {
this._current = this._current || d;
let interpolate = d3.interpolate(this._current, d);
this._current = interpolate(0);
return function(t) {
let d2 = interpolate(t);
let pos = self.arc.centroid(d2);
pos[0] = self.radius * (midAngle(d2) < Math.PI ? 1 : -1);
return `translate(${pos})`;
}
});
...
}
虽然您的 const self = this;
来捕获外部 this
范围。这在某种程度上感觉像是在回避您自己的问题,该问题要求 ES6 解决方案。
此方法的 ES6 替代方案是使用 arrow function。箭头函数的一个好处是,它们从定义它们的封闭范围(词法)中选择它们的 this
,而传统函数有它们自己的 this
阻止您访问外部范围。这使得箭头函数作为 OOP 中使用的回调特别有用,您希望从回调中访问实例的属性。
可以很容易地重写您的代码以利用此功能:
drawChart() {
//...
// Use arrow function to lexically capture this scope.
const interpolator = t => {
let d2 = interpolate(t);
let pos = this.arc.centroid(d2);
pos[0] = this.radius * (midAngle(d2) < Math.PI ? 1 : -1);
return `translate(${pos})`;
};
labels
.transition()
.duration(666)
.attrTween("d", function(d) {
this._current = this._current || d;
let interpolate = d3.interpolate(this._current, d);
this._current = interpolate(0);
return interpolator;
});
//...
}
注意,这仍然使用普通函数作为提供给 .attrTween()
的 插值器工厂 回调,因为此函数依赖于 this
绑定到迭代的当前 DOM 元素而不是外部范围。
进一步阅读:Chapter 13. Arrow Functions from the excellent book Exploring ES6 Axel Rauschmayer 博士。
由于我无法对已接受的
drawChart() {
//...
// Use arrow function to lexically capture this scope.
const interpolator = t => {
let d2 = interpolate(t); // <-- Reference Error
let pos = this.arc.centroid(d2);
pos[0] = this.radius * (midAngle(d2) < Math.PI ? 1 : -1);
return `translate(${pos})`;
};
labels
.transition()
.duration(666)
.attrTween("d", function(d) {
this._current = this._current || d;
let interpolate = d3.interpolate(this._current, d); // <-- Declared here
this._current = interpolate(0);
return interpolator;
});
//...
}
ps. 我发现:
const self = this;
在这种情况下是一个很好的解决方案,因为它更容易阅读和推理,即使它不是最好的 ES6 方式。