大家好, 我正在尝试使用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 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()方法中创建的,它也会给我未定义。
答案 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中设置了完整的演示。