首页 > javascript > d3在组SVG中拖动多个文本元素

d3在组SVG中拖动多个文本元素 (d3 drag multiple text elements inside a group SVG)

问题

我是新手使用d3.js. 我试图在svg组中追加多个文本元素。有了这个,我希望能够拖动多个文本组。
举个例子:

    export function testNode (config = {}) {
    let { svg, rectX, rectY, text1, text2 } = config
    var data = [{ x: rectX, y:rectY, label: text1, label2: text2, labelX: rectX + 100, labelY: rectY + 200, labelX2: rectX + 300, labelY2: rectY + 300 }]
        var group = svg.append("g")
                .selectAll("g")
        .data(data)
        .enter()
                .append("g")
                .attr("transform",
                    "translate(" + 0 + "," + 0 + ")")
                .call(d3.drag()
                    .on("start", dragstarted)
                    .on("drag", dragged)
                    .on("end", dragended));

        group.append("text")
                .data(data)
                .attr("x", (d) => { return d.labelX })
                .attr("y", (d) => { return d.labelY })
                .attr("font-size", "1em")
                .attr("color", "black")
                .text((d) => { return d.label });

            group.append("text")
                .data(data)
                .attr("x", (d) => { return d.labelX2 })
                .attr("y", (d) => { return d.labelY2 })
                .attr("font-size", ".75em")
                .attr("color", "black")
                .attr("class", "label")
                .text((d) => { return d.metricValue_01 });

function dragStarted() { 
 d3.select(this).raise().classed("active", true);
}

function dragged(d) {
d3.select(this).select("text")
 .attr("x", d.labelX = d3.event.x + 10)
 .attr("y", d.labelY = d3.event.y + 20);

d3.select(this).select("text")
 .attr("x", d.labelX2 = d3.event.x + 10)
 .attr("y", d.labelY2 = d3.event.y + 20);

function dragended() {
    d3.select(this).classed("active", false);
}

如果我使用selectAll方法,文本会聚集在一起。因此,我想知道是否可以根据我给它的坐标(在被拖动时)将文本组拖动到正确的位置。无论如何,如果我需要提供任何进一步的信息,请告诉我。谢谢

解决方法

如果要选择第二个标签,但不想selectAll同时选择它们,可以在追加时给出标签类,并在拖动时选择它们:

d3.select(this).select(".bigLabel")
 .attr("x", d.labelX = d3.event.x + 10)
 .attr("y", d.labelY = d3.event.y + 20);

d3.select(this).select(".smallLabel")
 .attr("x", d.labelX2 = d3.event.x + 10)
 .attr("y", d.labelY2 = d3.event.y + 20);

当然,除非您指定偏移量,否则这将为两个标签设置相同的坐标,如下所示:

var data = [
 { x: 100, x2: 100, y: 100, y2: 120, label: "label1", value: "17%" },
 { x: 300, x2: 300, y: 200, y2: 220, label: "label2", value: "83%" },
 { x: 100, x2: 100, y: 200, y2: 220, label: "label3", value: "11%" },
 { x: 300, x2: 300, y: 100, y2: 120, label: "label4", value: "96%" }
];

var svg = d3.select("svg");

var labels = svg.selectAll("g")
  .data(data)
  .enter()
  .append("g")
  .call(d3.drag()
  .on("drag",drag));
    
labels.append("text")
  .attr("font-size", "1em")
  .attr("x", function(d) { return d.x;})
  .attr("y", function(d) { return d.y;})
  .text(function(d) { return d.label; })
  .attr("class","label1");
     
labels.append("text")
  .attr("font-size", ".75em")
  .attr("x", function(d) { return d.x2;})
  .attr("y", function(d) { return d.y2;})  
  .text(function(d) { return d.value; })
  .attr("class","label2");

function drag(d) {
  var x = d3.event.x;
  var y = d3.event.y;
  d3.select(this)
    .select(".label1")
    .attr("x", function(d) { return d.x = x; })
    .attr("y", function(d) { return d.y = y; })
  d3.select(this)
    .select(".label2")
    .attr("x", function(d) { return d.x2 = x; })
    .attr("y", function(d) { return d.y2 = y + 20; })    
    
}
text {
  text-anchor:middle;
  cursor:pointer;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg width="500" height="400"></svg>


我不会深入研究替代方案,但它会将拖动应用于g同时定位两个文本标签。这可以比上面更容易处理兄弟标签之间的不规则间距:

var data = [
 { x: 100, y: 100, label: "label1", value: "17%" },
 { x: 300, y: 200, label: "label2", value: "83%" },
 { x: 100, y: 200, label: "label3", value: "11%" },
 { x: 300, y: 100, label: "label4", value: "96%" }
];

var svg = d3.select("svg");

var labels = svg.selectAll("g")
  .data(data)
  .enter()
  .append("g")
  .attr("transform",function(d) { 
    return "translate("+[d.x,d.y]+")"; 
  })
  .call(d3.drag().on("drag", drag));
    
labels.append("text")
  .attr("font-size", "1em")
  .text(function(d) { return d.label; });
     
labels.append("text")
  .attr("font-size", ".75em")
  .text(function(d) { return d.value; })
  .attr("dy", "1em")
  
function drag(d) {
  d3.select(this)
    .attr("transform","translate("+[d.x=d3.event.x,d.y=d3.event.y]+")"); 
}
text {
  text-anchor: middle;
  cursor: pointer;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg width="500" height="400"></svg>


此外,没有必要使用append("text").data(data),这没有做任何事情 - 您的数据已经绑定到新附加的元素

最后,如果在设置属性时使用任何提供的函数的第二个参数设置属性,则可以使用selectAll():(d,i)=>...i是元素的索引,因此如果您的兄弟标签是规则间隔的,您可以使用如下内容:

var data = [
 { x: 100, y: 100, label: "label1", value: "17%" },
 { x: 300, y: 200, label: "label2", value: "83%" },
 { x: 100, y: 200, label: "label3", value: "11%" },
 { x: 300, y: 100, label: "label4", value: "96%" }
];

var svg = d3.select("svg");

var labels = svg.selectAll("g")
  .data(data)
  .enter()
  .append("g")
  .call(d3.drag()
  .on("drag",drag));
    
labels.append("text")
  .attr("font-size", "1em")
  .attr("x", function(d) { return d.x;})
  .attr("y", function(d) { return d.y;})
  .text(function(d) { return d.label; })
     
labels.append("text")
  .attr("font-size", ".75em")
  .attr("x", function(d) { return d.x;})
  .attr("y", function(d) { return d.y + 20;})  
  .text(function(d) { return d.value; })

function drag(d) {
  var x = d3.event.x;
  var y = d3.event.y;
  d3.select(this)
    .selectAll("text")
    .attr("x", function(d) { return d.x = x; })
    .attr("y", function(d,i) { d.y = y;
      return d.y + i * 20;
    })
}
text {
  text-anchor:middle;
  cursor:pointer;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg width="500" height="400"></svg>


问题

I am new to using d3.js. I am trying to append multiple text elements in a svg group. With that, I want to be able to drag the group of multiple text.
So for an example:

    export function testNode (config = {}) {
    let { svg, rectX, rectY, text1, text2 } = config
    var data = [{ x: rectX, y:rectY, label: text1, label2: text2, labelX: rectX + 100, labelY: rectY + 200, labelX2: rectX + 300, labelY2: rectY + 300 }]
        var group = svg.append("g")
                .selectAll("g")
        .data(data)
        .enter()
                .append("g")
                .attr("transform",
                    "translate(" + 0 + "," + 0 + ")")
                .call(d3.drag()
                    .on("start", dragstarted)
                    .on("drag", dragged)
                    .on("end", dragended));

        group.append("text")
                .data(data)
                .attr("x", (d) => { return d.labelX })
                .attr("y", (d) => { return d.labelY })
                .attr("font-size", "1em")
                .attr("color", "black")
                .text((d) => { return d.label });

            group.append("text")
                .data(data)
                .attr("x", (d) => { return d.labelX2 })
                .attr("y", (d) => { return d.labelY2 })
                .attr("font-size", ".75em")
                .attr("color", "black")
                .attr("class", "label")
                .text((d) => { return d.metricValue_01 });

function dragStarted() { 
 d3.select(this).raise().classed("active", true);
}

function dragged(d) {
d3.select(this).select("text")
 .attr("x", d.labelX = d3.event.x + 10)
 .attr("y", d.labelY = d3.event.y + 20);

d3.select(this).select("text")
 .attr("x", d.labelX2 = d3.event.x + 10)
 .attr("y", d.labelY2 = d3.event.y + 20);

function dragended() {
    d3.select(this).classed("active", false);
}

If I use the selectAll method, the text gets clumped together. Therefore, I was wondering if I can drag the text group in their proper position based on the coordinate I give it (while being dragged). Anyways please let me know if I need to provide any further information. Thank you

解决方法

If you want to select the second label, but don't want to use selectAll as it selects both, you can give the labels classes when appending and select those when dragging:

d3.select(this).select(".bigLabel")
 .attr("x", d.labelX = d3.event.x + 10)
 .attr("y", d.labelY = d3.event.y + 20);

d3.select(this).select(".smallLabel")
 .attr("x", d.labelX2 = d3.event.x + 10)
 .attr("y", d.labelY2 = d3.event.y + 20);

Though of course this will set the same coordinate for both labels unless you specify an offset, as below:

var data = [
 { x: 100, x2: 100, y: 100, y2: 120, label: "label1", value: "17%" },
 { x: 300, x2: 300, y: 200, y2: 220, label: "label2", value: "83%" },
 { x: 100, x2: 100, y: 200, y2: 220, label: "label3", value: "11%" },
 { x: 300, x2: 300, y: 100, y2: 120, label: "label4", value: "96%" }
];

var svg = d3.select("svg");

var labels = svg.selectAll("g")
  .data(data)
  .enter()
  .append("g")
  .call(d3.drag()
  .on("drag",drag));
    
labels.append("text")
  .attr("font-size", "1em")
  .attr("x", function(d) { return d.x;})
  .attr("y", function(d) { return d.y;})
  .text(function(d) { return d.label; })
  .attr("class","label1");
     
labels.append("text")
  .attr("font-size", ".75em")
  .attr("x", function(d) { return d.x2;})
  .attr("y", function(d) { return d.y2;})  
  .text(function(d) { return d.value; })
  .attr("class","label2");

function drag(d) {
  var x = d3.event.x;
  var y = d3.event.y;
  d3.select(this)
    .select(".label1")
    .attr("x", function(d) { return d.x = x; })
    .attr("y", function(d) { return d.y = y; })
  d3.select(this)
    .select(".label2")
    .attr("x", function(d) { return d.x2 = x; })
    .attr("y", function(d) { return d.y2 = y + 20; })    
    
}
text {
  text-anchor:middle;
  cursor:pointer;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg width="500" height="400"></svg>

I won't dive into the alternative in too much depth, but it applies the drag to the g, positioning both text labels at the same time. This can handle irregular spacing between sibling labels easier than the above:

var data = [
 { x: 100, y: 100, label: "label1", value: "17%" },
 { x: 300, y: 200, label: "label2", value: "83%" },
 { x: 100, y: 200, label: "label3", value: "11%" },
 { x: 300, y: 100, label: "label4", value: "96%" }
];

var svg = d3.select("svg");

var labels = svg.selectAll("g")
  .data(data)
  .enter()
  .append("g")
  .attr("transform",function(d) { 
    return "translate("+[d.x,d.y]+")"; 
  })
  .call(d3.drag().on("drag", drag));
    
labels.append("text")
  .attr("font-size", "1em")
  .text(function(d) { return d.label; });
     
labels.append("text")
  .attr("font-size", ".75em")
  .text(function(d) { return d.value; })
  .attr("dy", "1em")
  
function drag(d) {
  d3.select(this)
    .attr("transform","translate("+[d.x=d3.event.x,d.y=d3.event.y]+")"); 
}
text {
  text-anchor: middle;
  cursor: pointer;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg width="500" height="400"></svg>

Also, there is no need to use append("text").data(data), this is not doing anything - your datum is already bound to the newly appended element

Lastly, you can make this work with selectAll() if you set attributes using the second parameter of any provided function when setting attributes: (d,i)=>... i is the index of the element, so if your sibling labels are regularly spaced, you can use something like:

var data = [
 { x: 100, y: 100, label: "label1", value: "17%" },
 { x: 300, y: 200, label: "label2", value: "83%" },
 { x: 100, y: 200, label: "label3", value: "11%" },
 { x: 300, y: 100, label: "label4", value: "96%" }
];

var svg = d3.select("svg");

var labels = svg.selectAll("g")
  .data(data)
  .enter()
  .append("g")
  .call(d3.drag()
  .on("drag",drag));
    
labels.append("text")
  .attr("font-size", "1em")
  .attr("x", function(d) { return d.x;})
  .attr("y", function(d) { return d.y;})
  .text(function(d) { return d.label; })
     
labels.append("text")
  .attr("font-size", ".75em")
  .attr("x", function(d) { return d.x;})
  .attr("y", function(d) { return d.y + 20;})  
  .text(function(d) { return d.value; })

function drag(d) {
  var x = d3.event.x;
  var y = d3.event.y;
  d3.select(this)
    .selectAll("text")
    .attr("x", function(d) { return d.x = x; })
    .attr("y", function(d,i) { d.y = y;
      return d.y + i * 20;
    })
}
text {
  text-anchor:middle;
  cursor:pointer;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg width="500" height="400"></svg>

相似信息