2017-12-10 3 views
3

Hallo Jungs, Ich versuche, ein D3 Visualisierung Netzwerk-Graphen in einem Winkel CLI-Projekt (http://bl.ocks.org/mbostock/1153292) unter Verwendung der ng2-nvd3 Komponente zu integrieren.D3.js Angular CLI, Scope in tick verloren() -Methode

Unten ist die Winkelkomponente:

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) 

Wenn die Web-Anwendung geladen wird, ist es auch die Grafik geladen, aber es ist nicht sichtbar: Picture of generated graph

Wenn ich zum Beispiel this.path in der Methode tick() trog, gibt es mir undefined, obwohl es in der Methode appendGraph() erstellt wurde.

Antwort

1

Dies ist eine Variation von "setTimeout() inside JavaScript Class using “this”". Das Force-Layout von D3 verwendet setTimeout(), um seine Ticks zu planen, was dazu führt, dass Ihre tick()-Funktion den falschen Bereich hat, sobald sie tatsächlich ausgeführt wird.

Werfen Sie einen Blick auf diese JSFiddle, die das Problem zeigt.

Im Gegensatz zu der obigen verknüpften Frage wird setTimeout() jedoch von D3 statt von Ihrem eigenen Code aufgerufen, der eine andere Problemumgehung erfordert, um den Bereich zu erhalten, den Sie benötigen. In diesem Fall können Sie einen Verschluss eine Referenz von this in Ihrem tick() Verfahren zu halten:

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); 
    } 
} 

Neben diesen Anpassungen müssen Sie die Initialisierung des Kraft Layouts korrigieren .tick() als Generator nennen, die die tatsächliche Zecke zurück Handler-Funktion, die einen Verweis auf den Bereich this Ihrer Klasseninstanz enthält.

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

stelle ich in diesem JSFiddle eine voll funktionsfähige Demo-up.