Incorporating D3.js into Angular 6 for interactive click events

Currently working on building a visual representation of a tree/hierarchy data structure using d3.js v4 within an Angular environment. I've taken inspiration from this particular implementation https://bl.ocks.org/d3noob/43a860bc0024792f8803bba8ca0d5ecd and integrated it into a component, encountering an issue with the 'click to expand/contract' functionality. After refreshing, the visualization renders correctly, but upon clicking a node, the following error is thrown:

ERROR TypeError: this.update is not a function
Stack trace:
./src/app/components/data-displayer.component.ts/DataDisplayerComponent.prototype.click@http://localhost:4200/main.js:304:9
contextListener/<@http://localhost:4200/vendor.js:69524:7
./node_modules/zone.js/dist/zone.js/</ZoneDelegate.prototype.invokeTask@http://localhost:4200/polyfills.js:2743:17
onInvokeTask@http://localhost:4200/vendor.js:33761:24
./node_modules/zone.js/dist/zone.js/</ZoneDelegate.prototype.invokeTask@http://localhost:4200/polyfills.js:2742:17
./node_modules/zone.js/dist/zone.js/</Zone.prototype.runTask@http://localhost:4200/polyfills.js:2510:28
./node_modules/zone.js/dist/zone.js/</ZoneTask.invokeTask@http://localhost:4200/polyfills.js:2818:24
invokeTask@http://localhost:4200/polyfills.js:3862:9
globalZoneAwareCallback@http://localhost:4200/polyfills.js:3888:17
core.js:1598

Upon triggering the onclick event, it appears that the subroutine (this.click) loses visibility of the other parts of the class (I validated this by logging the class fields, all of which were logged as undefined). Although the correct data is still passed, the this.update method becomes uncallable. Version details:

angular: 6.0.1, Node: 8.9.1, OS: win32 x64

import { Component, Input, OnInit, ViewEncapsulation } from "@angular/core";
import * as d3 from "d3";
import { HierarchyPointNode } from "d3";

export const margin = { top: 20, right: 120, bottom: 20, left: 120 };
export const width = 960 - margin.right - margin.left;
export const height = 800 - margin.top - margin.bottom;

@Component({
    selector: "data-displayer",
    template: "<svg></svg>",
    styleUrls: ["data-displayer.component.css"],
    providers: [],
    encapsulation: ViewEncapsulation.None,
})
export class DataDisplayerComponent implements OnInit {
    private svg;
    private treeLayout;
    private root;

    ngOnInit() {
        d3.json("../../assets/flare.json").then(data => {

            this.root = d3.hierarchy(data, (d) => d.children);
            this.root.x0 = height / 2;
            this.root.y0 = 0;

            let collapse = function (d) {
                if (d.children) {
                    d._children = d.children;
                    d._children.forEach(collapse);
                    d.children = null;
                }
            }    

            this.root.children.forEach(collapse);

            this.update(this.root);
        });

        this.svg = d3.select("svg")
            .attr("width", width + margin.right + margin.left)
            .attr("height", height + margin.top + margin.bottom)
            .append("g")
            .attr("transform", "translate(" + margin.left + "," + margin.top                     + ")");

    this.treeLayout = d3.tree().size([height, width]);

}

update(source) {
    let i = 0;
    let duration = 750;

    let treeData = this.treeLayout(this.root);
    let nodes = treeData.descendants();
    let links = treeData.descendants().slice(1);

    nodes.forEach(d => d.y = d.depth * 180);

    let node = this.svg.selectAll("g.node")
        .data(nodes, d =>  d.id || (d.id = ++i) );

    let nodeEnter = node.enter().append("g")
        .attr("class", "node")
        .attr("transform", d => "translate(" + source.y0 + "," + source.x0 + ")")
        .on("click", this.click);

    nodeEnter.append("circle")
        .attr("class", "node")
        .attr("r", 1e-6)
        .style("fill", d => d._children ? "lightsteelblue" : "#fff");

    let nodeUpdate = nodeEnter.merge(node);

    nodeUpdate.transition()
        .duration(duration)
        .attr("transform", d => "translate(" + d.y + "," + d.x + ")");

    nodeUpdate.select("circle.node")
        .attr("r", 10)
        .style("fill", d => d._children ? "lightsteelblue" : "#fff")
        .attr("cursor", "pointer");

    let nodeExit = node.exit().transition()
        .duration(duration)
        .attr("transform", d => "translate(" + source.y + "," + source.x + ")")
        .remove();

    nodeExit.select("circle")
        .attr("r", 1e-6);

    nodeExit.select("text")
        .style("fill-opacity", 1e-6);

    let link = this.svg.selectAll("path.link")
        .data(links, d => d.id);

    let linkEnter = link.enter().insert("path", "g")
        .attr("class", "link")
        .attr("d", d => {
            let o = { x: source.x0, y: source.y0 };
            return this.diagonal(o, o);
        });

    let linkUpdate = linkEnter.merge(link);

    linkUpdate.transition()
        .duration(duration)
        .attr("d", d => {
            return this.diagonal(d, d.parent)
        });

    let linkExit = link.exit().transition()
        .duration(duration)
        .attr("d", d => {
            let o = { x: source.x, y: source.y };
            return this.diagonal(o, o);
        })
        .remove();

    nodes.forEach(d => {
        d.x0 = d.x;
        d.y0 = d.y;
    });

}

click(d) {
    if (d.children) {
        d._children = d.children;
        d.children = null;
    } else {
        d.children = d._children;
        d._children = null;
    }

    this.update(d);
}

diagonal(s, d) {
    let path = `M ${s.y} ${s.x}
            C ${(s.y + d.y) / 2} ${s.x},
              ${(s.y + d.y) / 2} ${d.x},
              ${d.y} ${d.x}`

    return path;
}

Answer №1

Ensuring the proper binding of this is crucial for the onclick method to work correctly:

.on("click", this.click.bind(this));

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

The struggle between Node.js 404 errors and Angular's URL refresh issue

I am currently developing a Node.js and AngularJS application. I encountered an issue where a page loads successfully when the URL is entered, but then I added a script to redirect to a 404 error page. Now, only one of the criteria works at a time - either ...

Error: ng-messages syntax issue with the field parameter

Encountering the following error: Syntax Error: Token '{' invalid key at column 2 of the expression [{{field}}.$error] starting at [{field}}.$error]. when attempting to execute the code below (form-field.html) <div class='row form-grou ...

Exploring the power of Angular Module Federation in leveraging remote services

I am breaking down an Angular application into multiple microfrontends. One of these microfrontends manages the shopping cart, and it includes a service for storing added products. The main shell microfrontend needs to access this service to display the nu ...

Ways to determine if an AngularJS modal is currently displayed

I am currently in the process of determining whether a modal is opened or closed. However, I keep encountering an error that says "cannot read property of open." To address this issue, I realize that I need to connect with $modal.open and retrieve the resu ...

Display a preview window once the image has been chosen

I have created an image preview div to show the selected image's thumbnail. Everything was working fine so far. But now, I want this div to be hidden when the page initially loads and only become visible when a user selects an image to upload. Here is ...

Determine the originating page in Next.js that leads to the current page

Imagine a scenario with three pages: A, B, and C. In this setup, it is possible to navigate from page A to C, as well as from page B to C. However, the displayed content on page C will vary depending on the origin page of the navigation. I am curious to ...

Assigning an interface to a useFetch object in Vuejs 3 and Nuxtjs 3: A step-by-step guide

Need assistance with assigning an interface to a useFetch object in Vuejs 3 Interface export interface ProdutoInterface { codigo: number nome: string } Componente const { data: produto, error } = await useFetch(config.API_BASE_URL+`/produto`) I&apos ...

The issue arises in Selenium IDE when a variable is mistakenly identified as a string instead of a

Hey there, I've encountered an issue while using Selenium IDE. I'm trying to increment a variable by two, but instead of performing numerical addition, it seems to be concatenating strings. <tr> <td>store</td> <td> ...

Customize React JS Material UI's InputBase to be responsive

https://i.stack.imgur.com/9iHM1.gif Link: codesandbox Upon reaching a certain threshold, like on mobile devices, the elements inside should stack vertically instead of horizontally, taking up full length individually. How can this be achieved? ...

What is the best method to extract an array of values or rows from my grid layout?

Looking for a way to convert my CSS-grid into a CSV format. I came across a helpful thread outlining how to structure the data in an array: How to export JavaScript array info to csv (on client side)?. Is there a method to extract all the div values in th ...

What is the best way to show nested objects in JavaScript?

let paragraph = document.createElement('p'); document.body.appendChild(paragraph) const fruit = { type: 'Apple', properties: { color: 'Green', price: { bulk: '$3/kg', smallQty: '$4/kg' ...

Use of image tag inside the title attribute

After coming across the question on how to add an image tag inside the title attribute of an anchor tag and finding only one answer claiming it's impossible, I stumbled upon a page where it was actually done: I decided to view the source of the page ...

Utilizing REST-API with Angular 2 and Electron

I am encountering an issue with my Electron App that utilizes Angular 2. I had to make a modification from <base href="/"> to <base href="./">, which is a relative path within the file system, in order to make it function properly. However, thi ...

Tips for detecting the end of a scroll view in a list view

I've encountered an issue with my scrollView that allows for infinite scrolling until the banner or container disappears. What I'm aiming for is to restrict scrolling once you reach the last section, like number 3, to prevent the name part from m ...

What is the best way to bring a JavaScript file from the source file into an index.html document

I am currently in the process of developing a system using React, and as someone new to the framework, I have encountered an issue. I need to include a JavaScript file in my index.html that is located within the src folder. This js file is essential for th ...

Utilizing long polling technique with jQuery/AJAX on the server end

Currently, I am facing an issue with long polling on a single page that contains multiple pages. The problem arises when a new request is made while a previous request is still processing. Even though I attempt to abort the previous request, it completes b ...

JavaScript code to change an array into a stringified format

I have encountered a challenge with transforming an array. The original array looks like this: array = [ { "name": "name", "value": "Olá" }, { "name": "age" ...

Which data types are needed for the HttpRequest in my interceptor for Angular 10 when using strict mode?

I've been delving into the realm of strict mode in Angular 10, which introduces the "no-any" tslint rule. I've put in the effort to eliminate the any type from my application, but I'm unsure what types I should use for the HttpRequ ...

Customize the file name for exporting data from a Syncfusion grid view with Angular

In my Angular (v6.8) application, I have implemented a Syncfusion grid view from Syncfusion. When downloading the grid content as an Excel sheet, the default file name displayed is "Export.xlsx". I am trying to set a custom file name for the download proce ...

Angular 5 Routerlink and button not working in IE and Firefox due to lack of response

Recently, I created a new Angular 5 CLI application and developed a navmenu component for the top section of the app. It functions flawlessly in Edge and Chrome - clicking on menu items follows the specified route in app.module.ts as expected. However, whe ...