首页 > 解决方案 > D3.js 将 realtive 拖动到图形 svg

问题描述

我的代码应该使用 D3.js v6 移动绘制在 svg 图形上的圆圈。圆圈应该被拖动到光标相对于图形的位置,但我认为给出的光标位置是相对于整个窗口而不是图形/svg。我不确定如何将鼠标位置修改为相对于 svg。我也尝试在这里使用此答案的建议:

d3 v6 指针函数未调整缩放和平移

编辑:

如果我要在 (0.5, 0.5) 这样的位置开始圆,我将如何制作它,以便圆只能沿着以 (0, 0) 为中心、半径为 0.5 的圆的圆周沿路径移动?我尝试使用以下方法将鼠标的位置缩放到 0.5 大小:

x = x*(0.5/(Math.sqrt(x**2 + y**2)));
y = y*(0.5/(Math.sqrt(x**2 + y**2)));

因为这是您如何将矢量缩放到一定长度,同时保持与此处所示相同的方向: https ://math.stackexchange.com/questions/897723/how-to-resize-a-vector-to-a-特定长度

然而,即使它应该将鼠标所在的任何点缩放到以原点为中心、半径为 0.5 的圆上,这似乎也不起作用。

var margin = {top: -20, right: 30, bottom: 40, left: 40};
//just setup
var svg = d3.select("#mysvg") 
    .attr("width", 300)
    .attr("height", 300)
    .append("g")
    .attr("transform",
        "translate(" + margin.left + "," + margin.top + ")");
    
var xAxis = d3.scaleLinear() 
    .domain([-1.5, 1.5])
    .range([0, 300]);

svg.append("g")
    .attr("transform", "translate(0," + 300 + ")")
    .call(d3.axisBottom(xAxis)); 

var yAxis = d3.scaleLinear() 
    .domain([-1.5, 1.5])
    .range([300, 0]);

svg.append("g")
    .call(d3.axisLeft(yAxis)); 
    
var circle1 = svg.append('circle')
    .attr('id', 'circle1')
    .attr('cx', xAxis(0))
    .attr('cy', yAxis(0))
    .attr('r', 10)
    .style('fill', '#000000')
    .call( d3.drag().on('drag', dragCircle) ); //add drag listener
    
function dragCircle(event) {
    
        let x = d3.pointer(event, svg.node())[0];
        let y = d3.pointer(event, svg.node())[1];    

        console.log("x: " + x + " y: " + y);
        d3.select("#circle1")
            .attr("cx", xAxis(x))
            .attr("cy", yAxis(y));
    } 
    
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.0.0/d3.min.js"></script>
<svg id="mysvg"></svg>

标签: javascriptd3.js

解决方案


两件事情,

D3.pointer 以像素为单位返回单位,这些不需要缩放 - 缩放的目的是采用任意单位并将其转换为像素:

所以而不是:

    d3.select("#circle1")
        .attr("cx", xAxis(x))
        .attr("cy", yAxis(y));

尝试:

    d3.select("#circle1")
        .attr("cx", x)
        .attr("cy", y);

此外,我们希望拖动相对于g保存绘图并对其应用变换的拖动。这部分详细说明了您链接到的问题。我们可以指定我们希望拖动相对于父级g

    let x = d3.pointer(event,svg.node())[0];
    let y = d3.pointer(event,svg.node())[1];

然后我们可以使用一些三角函数将点约束到一个环上,并让阻力基于与任意点的角度:

        let x = d3.pointer(event,svg.node())[0];
        let y = d3.pointer(event,svg.node())[1]; 
        
        let cx = xAxis(0);
        let cy = yAxis(0);
        let r = xAxis(0.5)-xAxis(0);
        
        let dx = x- cx;
        let dy = y-cy;
        
        var angle = Math.atan2(dy,dx);

        d3.select("#circle1")
            .attr("cx", cx + Math.cos(angle)*r)
            .attr("cy", cy + Math.sin(angle)*r);

这给了我们:

var margin = {top: -20, right: 30, bottom: 40, left: 40};
//just setup
var svg = d3.select("#mysvg") 
    .attr("width", 300)
    .attr("height", 300)
    .append("g")
    .attr("transform",
        "translate(" + margin.left + "," + margin.top + ")");
    
var xAxis = d3.scaleLinear() 
    .domain([-1.5, 1.5])
    .range([0, 300]);

svg.append("g")
    .attr("transform", "translate(0," + 300 + ")")
    .call(d3.axisBottom(xAxis)); 

var yAxis = d3.scaleLinear() 
    .domain([-1.5, 1.5])
    .range([300, 0]);

svg.append("g")
    .call(d3.axisLeft(yAxis)); 
    
var circle1 = svg.append('circle')
    .attr('id', 'circle1')
    .attr('cx', xAxis(0))
    .attr('cy', yAxis(0))
    .attr('r', 10)
    .style('fill', '#000000')
    .call( d3.drag().on('drag', dragCircle) ); //add drag listener
    
var dragCircle = svg.append('circle')
    .attr('id', 'circle1')
    .attr('cx', xAxis(0))
    .attr('cy', yAxis(0))
    .attr('r', xAxis(0.5)-xAxis(0))
    .style('fill', 'none')
    .style('stroke', 'black')
    .style('stroke-line',1)
    .call( d3.drag().on('drag', dragCircle) ); //add drag listener
    
function dragCircle(event) {
    
        let x = d3.pointer(event,svg.node())[0];
        let y = d3.pointer(event,svg.node())[1]; 
        
        let cx = xAxis(0);
        let cy = yAxis(0);
        let r = xAxis(0.5)-xAxis(0);
        
        let dx = x- cx;
        let dy = y-cy;
        
        var angle = Math.atan2(dy,dx);

        d3.select("#circle1")
            .attr("cx", cx + Math.cos(angle)*r)
            .attr("cy", cy + Math.sin(angle)*r);
    }
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/6.0.0/d3.min.js"></script>
<svg id="mysvg"></svg>


推荐阅读