Search code examples
angulard3.jstypescriptevent-handlingencapsulation

D3.js: Passing a parameter to event handler


I have a D3.js code within an Angular 2 component, written in TypeScript.

Naturally, I tend to wrap things in an OOP way, so that the component can be (for instance) reused multiple times.

However, I have an issue with passing something to event handlers.

    this.simulation = d3.forceSimulation()
        ...
        .on("tick", this.onSimulationTick);

onSimulationTick() can only access global variables, d3.event and this:

When a specified event is dispatched, each listener will be invoked with the this context as the simulation.

Global variable is not an option, breaks encapsulation. I can't attach anything to d3.event, and I don't know what they mean by context.

In the handler, I want to access few things which are class members. So best would be to pass the component object.

How can I pass anything to the handler? How could I use the context for it?

Maybe I could use lambda in some way, like

.on("tick", () => onSimulationTick.that = this, onSimulationTick );

Here's the shortened component code:

@Component({
    templateUrl: "dependencies-graph.component.html",
    styleUrls: ["dependencies-graph.component.css"],
    selector: 'wu-dependencies-graph',
})
export class DependenciesGraphComponent implements OnInit, OnChanges {

    // Data
    _dependencies: DependenciesData;
    private jsonData;

    // Selections
    private zoomingGroup;

    // Behaviors
    private simulation;
    private zoom;
    private center: Point;

    private initVisualisation() {
        this.zoomingGroup = d3.select("svg #zoomingGroup");
        ...
        this.simulation = d3.forceSimulation()
            ...
            .on("tick", this.onSimulationTick);
    }

    static onSimulationTick() {
        ???.zoomingGroup.selectAll(".myEdge")
            .attr("x1", function(item) { return item.source.x; })
            .attr("y1", function(item) { return item.source.y; })
            .attr("x2", function(item) { return item.target.x; })
            .attr("y2", function(item) { return item.target.y; });

        ???.zoomingGroup.selectAll(".myGroup")
                .attr("transform", function(d){return "translate("+d.x+","+d.y+")"});
    }

Solution

  • You could bind context with Function.prototype.bind method::

    private initVisualisation() {
        this.zoomingGroup = d3.select("svg #zoomingGroup");
        ...
        this.simulation = d3.forceSimulation()
            ...
            .on("tick", this.onSimulationTick.bind(this));
    }
    
    static onSimulationTick() {
        this.zoomingGroup.selectAll(".myEdge")
            .attr("x1", function(item) { return item.source.x; })
            .attr("y1", function(item) { return item.source.y; })
            .attr("x2", function(item) { return item.target.x; })
            .attr("y2", function(item) { return item.target.y; });
    
        this.zoomingGroup.selectAll(".myGroup")
                .attr("transform", function(d){return "translate("+d.x+","+d.y+")"});
    }
    

    If you want to pass additional parameters arrow function might be better option:

    .on("tick", () => this.onSimulationTick(somethingElse));