D3.js Angular CLI,范围在tick()方法中丢失

时间:2017-12-10 17:45:07

标签: javascript angularjs d3.js graph angular-cli

大家好, 我正在尝试使用http://bl.ocks.org/mbostock/1153292组件将D3可视化网络图集成到Angular CLI项目(ng2-nvd3)中。

以下是Angular组件:

import { Component, OnInit } from '@angular/core';
declare let d3: any;

@Component({
  selector: 'app-visual',
  templateUrl: './visual.component.html',
  styleUrls: ['./visual.component.css']
})
export class VisualComponent implements OnInit {
   private links = [
    { source: "Microsoft", target: "Amazon", type: "licensing" },
    { source: "Microsoft", target: "HTC", type: "licensing" },
    { source: "Samsung", target: "Apple", type: "suit" },
    { source: "Motorola", target: "Apple", type: "suit" },
    { source: "Nokia", target: "Apple", type: "resolved" },
    { source: "HTC", target: "Apple", type: "suit" },
    { source: "Kodak", target: "Apple", type: "suit" },
    { source: "Microsoft", target: "Barnes & Noble", type: "suit" },
    { source: "Microsoft", target: "Foxconn", type: "suit" },
    { source: "Oracle", target: "Google", type: "suit" },
    { source: "Apple", target: "HTC", type: "suit" },
    { source: "Microsoft", target: "Inventec", type: "suit" },
    { source: "Samsung", target: "Kodak", type: "resolved" },
    { source: "LG", target: "Kodak", type: "resolved" },
    { source: "RIM", target: "Kodak", type: "suit" },
    { source: "Sony", target: "LG", type: "suit" },
    { source: "Kodak", target: "LG", type: "resolved" },
    { source: "Apple", target: "Nokia", type: "resolved" },
    { source: "Qualcomm", target: "Nokia", type: "resolved" },
    { source: "Apple", target: "Motorola", type: "suit" },
    { source: "Microsoft", target: "Motorola", type: "suit" },
    { source: "Motorola", target: "Microsoft", type: "suit" },
    { source: "Huawei", target: "ZTE", type: "suit" },
    { source: "Ericsson", target: "ZTE", type: "suit" },
    { source: "Kodak", target: "Samsung", type: "resolved" },
    { source: "Apple", target: "Samsung", type: "suit" },
    { source: "Kodak", target: "RIM", type: "suit" },
    { source: "Nokia", target: "Qualcomm", type: "suit" }
  ];
  private nodes: Array<Object> = [];
  private width = 960;
  private height = 500;
  private force: any;
  public svg: any;
  private path: any;
  private circle: any;
  private text: any;

  constructor(/*d3Service: D3Service*/) {
    // this.d3 = d3Service.getD3();
  }



ngOnInit() {
    // let d3 = this.d3;
    this.computeLinks(this.nodes);
    this.forceLayout();
    this.appendGraph();
  }

  computeLinks(nodes) {
    // Compute the distinct nodes from the links.
    this.links.forEach(function (link) {
      link.source = nodes[link.source] || (nodes[link.source] = { name: link.source });
      link.target = nodes[link.target] || (nodes[link.target] = { name: link.target });
    });
    this.nodes = nodes;
  }


  forceLayout() {
    this.force = d3.layout.force()
      .nodes(d3.values(this.nodes))
      .links(this.links)
      .size([this.width, this.height])
      .linkDistance(60)
      .charge(-300)
      .on("tick", this.tick)
      .start();
  }

  appendGraph() {
    this.svg = d3.select(".graph").append("svg")
      .attr("width", this.width)
      .attr("height", this.height);

    // Per-type markers, as they don't inherit styles.
    this.svg.append("defs").selectAll("marker")
      .data(["suit", "licensing", "resolved"])
      .enter().append("marker")
      .attr("id", function (d) { return d; })
      .attr("viewBox", "0 -5 10 10")
      .attr("refX", 15)
      .attr("refY", -1.5)
      .attr("markerWidth", 6)
      .attr("markerHeight", 6)
      .attr("orient", "auto")
      .append("path")
      .attr("d", "M0,-5L10,0L0,5");

    this.path = this.svg.append("g").selectAll("path")
      .data(this.force.links())
      .enter().append("path")
      .attr("class", function (d) { return "link " + d.type; })
      .attr("marker-end", function (d) { return "url(#" + d.type + ")"; });

    this.circle = this.svg.append("g").selectAll("circle")
      .data(this.force.nodes())
      .enter().append("circle")
      .attr("r", 6)
      .call(this.force.drag);

    this.text = this.svg.append("g").selectAll("text")
      .data(this.force.nodes())
      .enter().append("text")
      .attr("x", 8)
      .attr("y", ".31em")
      .text(function (d) { return d.name; });
  }
  tick() {
    this.path.attr("d", this.linkArc);
    this.circle.attr("transform", this.transform);
    this.text.attr("transform", this.transform);
  }

  linkArc(d) {
    var dx = d.target.x - d.source.x,
        dy = d.target.y - d.source.y,
        dr = Math.sqrt(dx * dx + dy * dy);
    return "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 0,1 " + d.target.x + "," + d.target.y;
  }

  transform(d) {
    return "translate(" + d.x + "," + d.y + ")";
  }
}

Error message

ERROR TypeError: Cannot read property 'attr' of undefined
at d3_dispatch.VisualComponent.tick (visual.component.ts:124)
at d3_dispatch.event [as tick] (d3.js:504)
at Object.force.tick [as c] (d3.js:6307)
at d3_timer_mark (d3.js:2166)
at d3_timer_step (d3.js:2147)
at ZoneDelegate.invokeTask (zone.js:425)
at Object.onInvokeTask (core.js:4747)
at ZoneDelegate.invokeTask (zone.js:424)
at Zone.runTask (zone.js:192)
at ZoneTask.invokeTask (zone.js:499)

加载Web应用程序时,它还会加载图形,但它根本不可见: Picture of generated graph

当我在tick()方法中控制日志this.path时,即使它是在appendGraph()方法中创建的,它也会给我未定义。

1 个答案:

答案 0 :(得分:1)

这是"setTimeout() inside JavaScript Class using “this”"的变体。 D3的强制布局将使用setTimeout()来安排其刻度,这会导致tick()函数在实际执行后具有错误的范围。

请看一下展示此问题的JSFiddle

但是,与上述链接问题相反,setTimeout()由D3调用,而不是由您自己的代码调用,这需要另一种解决方法来保留您需要的范围。在这种情况下,您可以使用闭包在this方法中保留tick()的引用:

tick() {
  let self = this;     // close over this
  return function() {
    // the function uses self throughout, which still references the instance of your class
    self.path.attr("d", self.linkArc);
    self.circle.attr("transform", self.transform);
    self.text.attr("transform", self.transform);
  }
}

除了这些调整之外,您还需要更正强制布局的初始化以调用.tick()作为生成器,该生成器返回实际的滴答处理函数,该函数保存对类实例的this范围的引用。

forceLayout() {
  this.force = d3.layout.force()
    //...omitted for brevity
    .on("tick", this.tick())   // Notice the parentheses after tick
    .start();
}

我在此JSFiddle中设置了完整的演示。