The value of scatter plot points does not remain consistent when zooming in using d3.js

I am new to using d3.js, so please bear with me as I try to implement it in a vue.js file with pure javascript. My goal is to create a scatter plot with zooming capabilities. While I have most of it working, I noticed an issue with the x-axis scaling during zooming. The y-axis scales properly, but the x-axis does not. For example, a point that was originally around 625 on the x-axis might be less than 600 after zooming. I suspect there is an error in the scaling of the x-axis within my zoom function, but I can't seem to pinpoint it. Could you please take a look and advise me on where I may have gone wrong?

Just to note, this project is utilizing d3.js version 7.4.4.

<template>
    <div id="reg_plot"></div>
</template>


<script>
import * as d3 from 'd3';
export default {
    name: 'regCamGraph',
    components: {
        d3
    },
    methods: {
        createSvg() {
            // dimensions
            var margin = {top: 20, right: 20, bottom: 30, left: 40},
                svg_dx = 1400, 
                svg_dy =1000,
                chart_dx = svg_dx - margin.right - margin.left,
                chart_dy = svg_dy - margin.top - margin.bottom;

            // data
            var y = d3.randomNormal(400, 100);
            var x_jitter = d3.randomUniform(-100, 1400);

            var d = d3.range(1000)
                        .map(function() { 
                            return [x_jitter(), y()]; 
                        });

            // fill
            var colorScale = d3.scaleLinear()
                                .domain(d3.extent(d, function(d) { return d[1]; }))
                                .range([0, 1]);


            // y position
            var yScale = d3.scaleLinear()
                            .domain(d3.extent(d, function(d) { return d[1]; }))
                            .range([chart_dy, margin.top]);
            
            // x position
            var xScale = d3.scaleLinear()
                            .domain(d3.extent(d, function(d) { return d[0]; }))
                            .range([margin.right, chart_dx]);
            console.log("chart_dy: " + chart_dy);
            console.log("margin.top: " + margin.top);
            console.log("chart_dx: " + chart_dx);
            console.log("margin.right: " + margin.right);
            // y-axis
            var yAxis = d3.axisLeft(yScale);

            // x-axis
            var xAxis = d3.axisBottom(xScale);

            // zoom
            var svg = d3.select("#reg_plot")
                        .append("svg")
                        .attr("width", svg_dx)
                        .attr("height", svg_dy);
            svg.call(d3.zoom().on("zoom", zoom));      // ref [1]

            // plot data
            var circles = svg.append("g")
                            .attr("id", "circles")
                            .attr("transform", "translate(200, 0)")
                            .selectAll("circle")
                            .data(d)
                            .enter()
                            .append("circle")
                            .attr("r", 4)
                            .attr("cx", function(d) { return xScale(d[0]); })
                            .attr("cy", function(d) { return yScale(d[1]); })
                            .style("fill", function(d) { 
                                var norm_color = colorScale(d[1]);
                                return d3.interpolateInferno(norm_color) 
                            });

            // add y-axis
            var y_axis = svg.append("g")
                            .attr("id", "y_axis")
                            .attr("transform", "translate(75,0)")
                            .call(yAxis).style("font-size", "20px")
                        
            // add x-axis
            var x_axis = svg.append("g")
                            .attr("id", "x_axis")
                            .attr("transform", `translate(${margin.left}, ${svg_dy - margin.bottom})`)
                            .call(xAxis).style("font-size", "20px")

            function zoom(e) {

                // re-scale y axis during zoom
                y_axis.transition()
                        .duration(50)
                        .call(yAxis.scale(e.transform.rescaleY(yScale)));

                // re-scale x axis during zoom
                x_axis.transition()
                        .duration(50)
                        .call(xAxis.scale(e.transform.rescaleX(xScale)));

                // re-draw circles using new y-axis scale
                var new_xScale = e.transform.rescaleX(xScale);
                var new_yScale = e.transform.rescaleY(yScale);

                console.log(d);

                x_axis.call(xAxis.scale(new_xScale));
                y_axis.call(yAxis.scale(new_yScale));
                circles.data(d)
                    .attr('cx', function(d){return new_xScale(d[0])})
                    .attr('cy', function(d){return new_yScale(d[1])});
            }

        }
    },
    mounted() {
        this.createSvg();
    }
    
}
</script>

Interestingly, setting the clip region to prevent displaying points outside of the axes seemed to resolve the issue. Here is how I created the clip path:

// clip path
            var clip = svg.append("defs").append("svg:clipPath")
            .attr("id", "clip")
            .append("svg:rect")
            .attr("id", "clip-rect")
            .attr("x", "0")
            .attr("y", "0")
            .attr('width', chart_dx)
            .attr('height', chart_dy);

Then, I added that attribute to the svg when plotting the data like this:

svg.append("g").attr("clip-path", "url(#clip)")

Updated clip path with plot data section:

// clip path
            var clip = svg.append("defs").append("svg:clipPath")
            .attr("id", "clip")
            .append("svg:rect")
            .attr("id", "clip-rect")
            .attr("x", "0")
            .attr("y", "0")
            .attr('width', chart_dx)
            .attr('height', chart_dy);

            // plot data
            var circles = svg.append("g")
                            .attr("id", "circles")
                            .attr("transform", "translate(75, 0)")
                            .attr("clip-path", "url(#clip)") //added here
                            .selectAll("circle")
                            .data(d)
                            .enter()
                            .append("circle")
                            .attr("r", 4)
                            .attr("cx", function(d) { return xScale(d[0]); })
                            .attr("cy", function(d) { return yScale(d[1]); })
                            .style("fill", function(d) { 
                                var norm_color = colorScale(d[1]);
                                return d3.interpolateInferno(norm_color) 
                            });

Answer №1

The issue has been successfully resolved. I have made an update to the original post detailing the solution that worked for me.

Essentially, introducing a clip region resolved the problem and ensured proper functionality of the elements.

// Implementation of clip path (new addition to prevent dots from extending beyond axes boundaries)
            var clip = svg.append("defs").append("svg:clipPath")
            .attr("id", "clip")
            .append("svg:rect")
            .attr("x", "0")
            .attr("y", "0")
            .attr('width', chart_dx)
            .attr('height', chart_dy);

            // Data plotting
            var circles = svg.append("g")
                            .attr("id", "circles")
                            .attr("transform", "translate(75, 0)")
                            .attr("clip-path", "url(#clip)") // Include clip region in svg 
                            .selectAll("circle")
                            .data(d)
                            .enter()
                            .append("circle")
                            .attr("r", 4)
                            .attr("cx", function(d) { return xScale(d[0]); })
                            .attr("cy", function(d) { return yScale(d[1]); })
                            .style("fill", function(d) { 
                                var norm_color = colorScale(d[1]);
                                return d3.interpolateInferno(norm_color) 
                            });

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

Use jQuery to open and close HTML tags efficiently

It seems like the task I have at hand may not be as simple as I had hoped, so here I am seeking some reassurance. I am aiming to switch out an image with a closing div, then the image itself, followed by another opening div. Let me illustrate this with a ...

Gathering all components prior to the comment

I am in the process of scraping information from a webpage. The data I require is contained within separate divs that have a specific class assigned to them. For instance: <div class="temp">text </div> The challenge arises when the number of ...

Generate unique identifiers to rotate images dynamically on the webpage

My goal is to rotate every image on a page, and while this works with a single image, it only rotates the first one since each ID needs to be unique. I need to find a way to dynamically increment the IDs as they are encountered on the page. Below is what I ...

What is the process for including jQuery js files in a React application?

I recently made the transition to React in order to create a single page application. Prior to this, I had 8 HTML files and 8 JS files that were linked using script tags like so: <script src="pathHere"> </script> With React, the HT ...

When the sidebar is closed, the jQuery Accordion tab should also close automatically

I have a sidebar that opens when the plus icon is clicked. Inside the sidebar, there is an accordion that can be opened independently. Even when the sidebar is closed, the accordion remains open. Is it possible to automatically close the accordion when th ...

I encountered an error with the TS1003 code in Angular because an identifier was expected on the throw import statement. Upon further investigation, I realized that the issue originated from

I came across this piece of code: import { Observable, throw} from 'rxjs'; An error message popped up saying identifier expected: ERROR in config/config.service.ts(3,22): error TS1003: Identifier expected. The error appears to be related to ...

Encountering difficulties with integrating map controls into the Nuxt3/Vue3 mapbox feature for zooming

Recently, I started exploring Mapbox within my new Nuxt3 application. I managed to successfully render the map in my custom Map.vue component. However, I am facing trouble when trying to add controls and other options. Despite my efforts, I can't see ...

Accessing an object within another object using Typescript

My goal is to access the "rename_fields" object within the main_object collection in order to utilize its field values: export interface StdMap<T = string> { [key: string]: T; } export type StdFileBasedPluginHandlerConfiguration< SourceTy ...

Can you explain the concept of using $addtoset with mongoskin in MongoDB to me? I am only slightly familiar with the English language

Unfortunately, I am unable to locate the answer as I have difficulty reading English. Which keyword should I use to find the answer to the code example shown below? for(var value in row){ db.collection('testdb').update({_id:'id'},{$ad ...

Exhibition of Expertise: NodeJS and Express Module Bypasses SQLite3 Database Inquiry

Currently, I am trying to establish a connection between a function in a separate JS file and my index file. However, during the execution of the function, it seems to skip over a certain part of the code, going back to the app.post method in the index.js ...

What steps can you take to prevent a potential crash from occurring when an unauthorized user attempts to access a page without logging in?

I've encountered an issue where the app crashes when trying to access a page that requires logging in. The reason for this crash is because the page attempts to load undefined data, resulting in the error: TypeError: Cannot read property 'firstN ...

"Ensure Playwright refreshes the page automatically following navigation when a specific status code is

I find myself in a dilemma where I require my functional browser tests to verify the status code of each page response, and if a 503 error is encountered, try to reload the page a certain number of times before declaring failure. Even though I have experi ...

Leaking Vuetify styles causing unexpected design issues

Recently, I created a small application using Vuetify and bundled it all into bundle.min.js, which includes CSS as well. However, upon importing the bundle into an index.html file like this: <script src=js/bundle.min.js></script> I noticed th ...

Extracting the chosen content from a textarea using AngularJS

Greetings! I am currently experimenting with an example that involves displaying values in a text area. You can find the code on Plunker by following this link: Plunker Link <!DOCTYPE html> <html> <head> <script src="https://aj ...

Having trouble connecting an array of objects to a Vuetify data table?

My Vuetify data table is not displaying any data in the body, even though I can see the Firestore data in the console log. Could it be because my items array has more data points than there are headers in the table? Vuetify Component <template> < ...

Vue.js has a feature where it automatically closes the form tag within a for loop

In my Vue.js application, I have created a table where each row is a form with a submit button. This is the code snippet I am using: <div id="admin-user"> <table class="table"> <tr v-for="(user, index) in users"> < ...

Dealing with client-side exceptions in a Next.js 13 application's directory error handling

After carefully following the provided instructions on error handling in the Routing: Error Handling documentation, I have successfully implemented both error.tsx and global-error.tsx components in nested routes as well as the root app directory. However, ...

Manipulating JSON with ng-model in AngularJS

Let's say I have a simple JSON similar to this: { "Object 0": {} } I am trying to display it as a tree structure. This is the approach I am taking: <span>{{key}}</span> // Object 0 <span>{{value}}</span> // {} <ul> ...

Ways to retrieve several URLs from a given text

Here is a string containing three URLs: "https://gaana.com/song/dil-chahte-hohttps://www.youtube.com/watch?v=MWnFCGXjjS0&list=PLs1-UdHIwbo5p-8wh740E7CRhIoKq5APmhttps://www.youtube.com/watch?v=MWnFCGXjjS0&list=PLs1-UdHIwbo5p-8wh740E7CRhIoKq5APm ...

Utilize JavaScript to assign an identifier to an element created with createElement()

As a beginner in JavaScript, I am trying to set an id into some created tags but it doesn't seem to be working. var d1=document.createElement("div"); d1.setAttribute("class","container"); var b3= document.createElement("button"); b3.setAttri ...