【问题标题】:Drawing Pie chart with ES6用 ES6 绘制饼图
【发布时间】:2018-03-27 13:56:47
【问题描述】:

我尝试在 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 中是否以及如何做到这一点。

【问题讨论】:

  • 要访问第一个范围内的const that = this;,然后在tween 函数中使用that 而不是this
  • 谢谢@RyanMorton,您的解决方案有效。我稍后会发布答案。

标签: javascript d3.js ecmascript-6 pie-chart es6-class


【解决方案1】:

由于我无法对已接受的 answer 发表评论(没有足够的声誉 :( )我只想指出新创建的内部函数 (interpolator) 将无法访问interpolateattrTween 中声明:

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 方式。

【讨论】:

    【解决方案2】:

    虽然您的answer 有效,但它是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()interpolator factory 回调,因为此函数依赖于 this 绑定到迭代的当前 DOM 元素,而不是外部范围。


    进一步阅读:Chapter 13. Arrow Functions 来自 Axel Rauschmayer 博士的优秀书籍 Exploring ES6

    【讨论】:

    • 太棒了,我真的很喜欢你的解决方案。谢谢你的分享。也感谢您的额外阅读,它确实有助于我更好地理解问题。
    【解决方案3】:

    我只想发布在@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})`;
                }
            });
    
        ...
    }
    

    【讨论】:

      猜你喜欢
      • 2014-05-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-12-27
      • 2019-01-03
      • 1970-01-01
      相关资源
      最近更新 更多