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)
});