How to implement mouse event handling on elements in a d3.js integrated Vue component?

After successfully working with plain JavaScript to create an interactive D3.js map, I am now attempting to convert it into a Vue.js component. I have defined all functions as methods, but I am facing a challenge where none of the mouse events are being triggered. I have tried using both the D3.js approach (.on("click", this.clickBG)) and the Vue approach (.attr("v-on:click","clickBG")), but neither method seems to be working.

    // Template: https://dev.to/ignoreintuition/binding-data-to-charts-using-vue-components-and-d3-4i27
    import * as d3 from 'd3';
    
    export default {
        props: ['data','vegbed'], 
        data: function () {
            return {
                gardenbox: {}
            }
        },
        computed: {
            displaywidth: function() { return 600; },
            displayheight: function() { return this.displaywidth*(this.vegbed.length/this.vegbed.width); },
            margin: function() { return {top: 0, right: 0, bottom: 0, left: 0}; }
        },
        methods: {
            initializeChart: function () {
                this.drawGardenBox();
            },

            virtualDiameter: function (realDia) {
                return (this.displaywidth/this.vegbed.width)*realDia;
            },
            
            clickPlant: function() {
                d3.selectAll(".selected").raise().classed("selected",false);
                d3.select(_this).selectAll(".plant").raise().classed("selected", true);
                d3.selectAll("#"+this.id).classed("selected", true);      
            },
            clickBackground: function() {
                console.log("click");
                d3.selectAll(".selected").raise().classed("selected",false);
            },
            
            draggedPlant: function(d) {
                d3.select(this).attr("transform","translate("+(d.x = d3.event.x)+","+(d.y = d3.event.y)+")");
            },
            
            dragStartedPlant: function() {
                d3.selectAll(".selected").raise().classed("selected",false);
                d3.select(this).selectAll(".plant").raise().classed("selected", true);
                d3.selectAll("#"+this.id).classed("selected", true);

                //d3.event.sourceEvent.stopPropagation();
            },    
            dragEndedPlant: function(d) {
                
            },
            refreshBed: function () {
                let _this = this; // Workaround to call functions from inside callbacks
                var gplant = this.gardenbox.selectAll("g")
                    .data(this.data).enter().append("g")
                    .attr("id", function(d){ return d.id; })
                    .attr("transform", function(d){ return "translate("+d.x+","+d.y+")";  })
                    .on("click", this.clickPlant)
                    //.attr("v-on:click","this.clickPlant")
                    .call(d3.drag()
                    .on("start", this.draggedPlant)
                    .on("drag", this.dragStartedPlant)
                    .on("end", this.dragEndedPlant));

                gplant.append("circle") // Max size
                    .attr('class', 'plant')
                    .attr("cx", function(d) { return d.x; })
                    .attr("cy", function(d) { return d.y; })
                    .attr("r", function(d) { return _this.virtualDiameter(d.plant.adult_diameter); });

                gplant.append("circle") // Min size
                    .attr('class', 'plant')
                    .attr("cx", function(d) { return d.x; })
                    .attr("cy", function(d) { return d.y; })
                    .attr("r", function(d) { return _this.virtualDiameter(d.plant.exposure_diameter); });

                gplant.append("image")
                    .attr('class', 'plant')
                    .attr("xlink:href", function(d) { return d.picref; })
                    .attr("x", function(d) { return d.x-(d.picsize/2); })
                    .attr("y", function(d) { return d.y-(d.picsize/2); })
                    .attr("width", function(d) { return d.picsize; })
                    .attr("height", function(d) { return d.picsize; });
            },
            
            drawGardenBox: function () {
                this.gardenbox = d3.select('#vegbedcontainer').append('svg')
                        .attr("width", this.displaywidth + this.margin.left + this.margin.right)
                        .attr("height", this.displayheight + this.margin.top + this.margin.bottom)
                // Background for deselect action
                var bg = this.gardenbox.append("g")
                        //.attr("v-on:click","clickBG");
                        .on("click", this.clickBackground); // Unselect on background click

                bg.append("rect") 
                    .attr('class', 'bg')
                    .attr("x", this.margin.left)
                    .attr("y", this.margin.top)
                    .attr("width", this.displaywidth + this.margin.left + this.margin.right)
                    .attr("height", this.displayheight + this.margin.top + this.margin.bottom)
            }
        },


        // Lifecycle events
        mounted: function () { // <-- Lifecycle events
            console.log('VegBedEditor mounted.')
            this.initializeChart();
            this.refreshBed();
        },
        // Watch functions
        watch: { // <-- Watch functions
            'data': {
                handler: function (val) {
                    this.refreshBed();
                },
                deep: true
            }
        },
        template: `<div id="vegbedcontainer"><!-- SVG goes here --></div>`
    }

Answer №1

After some experimentation, I discovered a solution that works perfectly in my Vue app.

Adding a Click Handler to SVG Background

drawGardenBox: function () {
  this.gardenbox = d3.select('#vegbedcontainer').append('svg')
    .attr("width", this.displaywidth + this.margin.left + this.margin.right)
    .attr("height", this.displayheight + this.margin.top + this.margin.bottom)

  var bg = d3.select('svg')
    .on("click", this.clickBG); // deselect on background click

Preventing Click Propagation from Another Handler

clickPlant: function() {
  d3.selectAll(".selected").raise().classed("selected",false);
  d3.select(_this).selectAll(".plant").raise().classed("selected", true);
  d3.selectAll("#"+this.id).classed("selected", true); 
  d3.event.stopPropagation(); 
},

Utilizing d3.event

Initially, I faced an issue with d3.event.stopPropagation() failing due to d3.event being null. Fortunately, I found a solution in this question: importing d3.event into a custom build using rollup. By adjusting the event import, everything now functions smoothly.

import { select, selectAll, event, customEvent } from 'd3-selection'
import { zoom } from 'd3-zoom'
import { tree, hierarchy } from 'd3-hierarchy'

export const d3 = {
  select,
  selectAll,
  tree,
  hierarchy,
  zoom,
  get event() { return event; },
  customEvent
}

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

Can't get className to work in VueJS function

I need help changing the classNames of elements with the "link" class. When I call a method via a click action, I can successfully get the length of the elements, but adding a class does not seem to work. Does anyone have any insights into this issue? HTM ...

I encountered an error stating "angular not found" when attempting to dynamically load my Angular scripts

My Angular application is running smoothly. However, I found it tedious to include multiple script tags in my HTML documents every time. To simplify this process, I decided to create a small script that would automatically generate the necessary tags for m ...

Conditional statement that includes Node.js scheduling function

I am currently working on a Node.js project and I need to execute a specific portion of conditional code after waiting for five minutes since the last piece of code executed. This action should only happen once, not on a daily basis or any other frequency. ...

Changing the position of an element in CSS using the center as the reference point, based on a JavaScript variable: How can it be done?

I have an image marking a scale, with the marker's position in HTML set as: <image class="red-marker" xlink:href="./assets/images/marker.png" [attr.x]=percentage width="12" height="40" y="0" ></image> But I want the middle of the marker ...

Identifying duplicated video duration data associated with videoIds in asynchronous calls using Youtube API v3

I've encountered an issue with the asynchronous methods that retrieve and display all vidDuration values followed by the corresponding viewCount values for each videoId. The problem arises when the vidDuration value repeats only the last received one ...

Guide to making a slider menu using html, css, and javascript

Just dipping my toes into the world of web development. I'm intrigued by the idea of creating a "slider menu" where users can view and select options by clicking on next or previous buttons (see example image below). While I've got some basic HTM ...

How can I extend the date range with JavaScript?

I am working with a variable called {{ $daterange }} that contains JSON data structured like this: { "starts_at": "2020-05-20", "ends_at": "2020-05-23" }, { "starts_at": "2020-05-24", "ends_at": "2020-05-26" }, { "starts_at": "2020-05- ...

Can Angular 4 experience race conditions?

Here is a snippet of my Angular 4 Service code: @Injectable() export class MyService { private myArray: string[] = []; constructor() { } private calculate(result): void { myArray.length = 0; // Perform calculations and add results to myAr ...

Troubleshooting: Issues with VueJS Class Binding Functionality

In my form, I have an input field that is validated to check if it is empty or not. If the input field is empty, a red border is applied using class binding. However, when the user focuses on the input field after receiving the error, the error message sh ...

tips for selecting various API requests based on the selected drop down menu choice

Hey there! I'm looking to enhance my login page by adding a feature that allows users to select from a dropdown menu with different options. Each option will be linked to a specific API, and based on the API response, the user's ability to log in ...

How to create an array of objects using an object

I have a specific object structure: { name: 'ABC', age: 12, timing: '2021-12-30T11:12:34.033Z' } My goal is to create an array of objects for each key in the above object, formatted like this: [ { fieldName: 'nam ...

Error with Vimeo SDK: Mysterious Player Issue Post Setup (VueJS)

I have a requirement to showcase multiple videos on a Vue-powered website using a Vimeo player. To achieve this, I have developed a VideoPlayer component specifically designed for each video: <template> <div class="video-element__containe ...

What are the steps to fix the "Invariant Violation" issue that is linked to the redux store?

During my DOM testing to verify if a dialog box would open upon clicking a button, I encountered an error message: Invariant Violation: Could not find "store" in either the context or props of >"Connect(Photos)". Either wrap the root component in a , ...

Eliminate repetitive elements from an array using a specific merging algorithm

Here's a thought I have: If we have an array of objects like this: [ { "name": "Kirk", "count": 1 }, { "name": "Spock", "count": 1 }, { "name": "Kirk", "count": 1 } ] I would l ...

How can you create a dynamic bounce effect for text with jquery animate()?

I've been experimenting with Jquery to achieve a bounce effect, here's what I have so far: Html: <div id ="animation">bounce</div> Jquery: $("#animation").animate({ marginTop: "80px" }, 1500 ) .animate({ marginBotto ...

Retrieve an element from an array using the POST method

I am currently working on implementing a POST method using mongo/mongoose: Department .create({ name: req.body.name, link: req.body.link, state: req.body.state, requirements: req.body.requirements, salary: req.b ...

Menu options are unresponsive to clicks in the dropdown

Need help fixing my dropdown menu issue. Whenever I hover over the dropdown, it disappears before I can click on any items. Here is a snippet of the code causing the problem: #navContainer { margin: 0; padding: 0; padding-top: 17px; width: 220 ...

Clients using Socket.io are known to establish connections with various sockets housed within the same server

Hello, I am currently managing a standard chatroom using socket.io but have encountered an issue that I am struggling to troubleshoot. The chatroom appears to be operating normally as clients can send and receive messages. However, there is an occasional ...

Sending a personalized event to a JavaScript object

I am looking to create an object Constructor that can dispatch a customEvent, which I then want to listen to from the instance of the object. The code snippet provided showcases what I have been attempting. I would appreciate any assistance on how to mak ...

Using a .vue file in an HTML document: A step-by-step guide

After creating a hello.vue file, I am unsure how to actually use it in an HTML file. I have already set up webpack, <template> <p>Hello</p> <p>{{message}}</p> </template> <script> module.exports = { data: ...