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

Using JavaScript/jQuery to analyze POST values just before submission

Currently facing a dilemma as my company is in the process of redesigning its website. I find myself stuck trying to come up with a temporary fix for an issue on the existing site. The solution requires the use of JavaScript or jQuery. The problem arises ...

Employing Bazel in conjunction with Lerna and Yarn workspaces

It seems that a lot of people are currently utilizing lerna and/or yarn workspace. I'm thinking about either transitioning from them to Bazel, or perhaps integrating them with Bazel in conjunction with an example project for guidance. Take for insta ...

Selenium - Tips for entering text in a dynamically generated text field using Javascript!

I'm fairly new to the world of web scraping and browser automation, so any guidance would be greatly appreciated! Using Python's Selenium package, my objective is: Navigate to Login using the provided username & password Complete my order thr ...

Http provider not found in Angular 4 when using Rails 5 API

I recently completed a tutorial on Angular and Rails which I found here, but I am encountering difficulties in implementing it successfully. Currently, I am working with Angular version 4.2.4. [Error] ERROR Error: No provider for Http! injectionError — ...

Tips for adjusting the header color in materialize framework?

I've been working on a web template and I'm looking to customize the color displayed in the Android browser (maybe using JS?). The default color is blue. How can I go about changing it? Appreciate any help! ...

Utilizing Glassfish Application Server and MSSQL Database for Angular Front-End Authentication in Jakarta

Embarking on my journey in the realm of web development, I am eager to implement authentication for my full-stack application. Armed with Angular 13 on the front end, Jakarta 9 running on Glassfish app server, and MSSQL database, I find myself at a loss on ...

Ways to insert script tag in a React/JSX document?

private get mouseGestureSettingView() { const {selectedMenu} = this.state; return ( selectedMenu == 2 ? <script src="../../assets/js/extensions/mouse-gesture/options.js"></script> <div className={styles.settingForm}& ...

Adjust the size of the plane in Three.js to match the entire view

English is not my strong suit, as I am Japanese. I apologize for any confusion. Currently, my focus is on studying Three.js. I aim to position a Plane directly in front of the camera as the background. My goal is to have the Plane background fill the en ...

When combining Socket.io 1.0 with express 4.2, the outcome is a lack of socket connection

According to the title, I am attempting to integrate socket.io 1.0.4 with express 4.2, but all requests with "/?EIO" are resulting in a 404 error. Below are my file configurations: ./bin/www : #!/usr/bin/env node var debug = require('debug')(& ...

Rearrange HTML list items automatically according to changes in numeric sort order

I am working on a web page that features a dynamic ranked list. The items in the list are assigned a specific rank order number which changes based on user interactions elsewhere on the page. I have been exploring different options to automatically reorgan ...

Erase the dynamically loaded page using ajax and conceal the div

Currently, I am utilizing the .on() method with jQuery to display a specific div, and also using .load() to fetch a particular div from a web page hosted on my server. My query is how can I close this div when clicking outside of it, along with removing i ...

What is the most effective way to conceal an element in Angular 4 by utilizing *ng-if?

Testing the functionality of @Input in angular 2 which can hide or show a p tag. I am looking to test this feature using jasmine. Below is my component: import { Component,Input } from '@angular/core'; @Component({ selector: 'my-app&apo ...

Utilizing AngularJS: Triggering a controller function from a directive

I am currently working on a project with a model named 'user', which includes a controller called 'login' and a directive called 'userMenu'. My goal is to have the userMenu directive utilize the 'login' controller th ...

The situation arose where Next.js could not access the cookie due to

Hi there, I'm new to web development and recently encountered a challenge with my next.js app. I'm currently following Brad Traversy's course on udemy to learn basic CRUD functions. In this component, I am trying to fetch user data from my ...

Troubleshooting Issue: Unable to Retrieve Loaded Record in Ember.js

I am facing an issue with accessing properties to change the header of my RESTAdapter after loading the user. Do you have any ideas why that might be happening? The code snippet in question is as follows: var user = ''; App.MainRoute = Ember.R ...

How to divide a string by space after a specific character count using Javascript

I've been working on a tool that can divide input text into chunks based on a specific character count without breaking words in the process. Currently, I have set it to split the text after 155 characters. Even after conducting extensive research, ...

I'm experiencing an issue with uploading an image to Firebase as I continue to encounter an error message stating "task.on is not a function."

The console displays a message confirming a successful upload. const sendPost = () => { const id = uuid(); const storage = getStorage(); const storageRef = ref(storage, `posts/${id}`) const uploadTask = uploadString(storageRe ...

JavaScript Radio Buttons

Below are the different radiobuttons: Apple <input type="radio" id="one" name="apple" data-price="10" value="light"/> Light <input type="radio" id="two" name="apple" data-price="20" value="dark" /> Dark <input type="text" id="appleqty" name ...

Utilizing a dynamic form action connected to an Express route

I've been grappling with creating an HTML form in my nodejs application that directs to the appropriate express route upon submission. After researching online, I stumbled upon a potential solution as outlined below: <script> $('#controlPa ...

To retrieve a property in Vue, you can use either the "this" keyword

Exploring Vue for the first time and navigating through accessing viewmodel data has me puzzled. When should I utilize this.property versus vm.$data.property. In this scenario with a table where I can select rows, there are methods in place to select all ...