首页 > 解决方案 > D3 在 v6 中拖放并单击分组对象

问题描述

根据Mike 的示例,我试图弄清楚如何将概念转移到分组元素。我可以单独创建启动拖动和单击,但在一起我似乎无法单击工作。下面是一个片段——这只是我使用过的许多方法中的一种。

  const width = 400;
  const height = 300;
  const radius = 5;
  
  const svg = d3.select("#chart").append('svg')
    .attr('width', '400')
    .attr('height', '400')
    .style('border', 'solid 1px');

  const circles = d3.range(20).map(i => ({
    x: Math.random() * (width - radius * 2) + radius,
    y: Math.random() * (height - radius * 2) + radius,
    index: i
  }));

const group =  svg.selectAll("g").data(circles).enter()
      .append("g")
      .attr("cx", d => d.x)
      .attr("cy", d => d.y)            
      .call(d3.drag()
      .on("start", dragstarted)
      .on("drag", dragged)
      .on("end", dragended))
  
  group.append("circle")
      .attr("cx", d => d.x)
      .attr("cy", d => d.y)            
      .attr("r", radius)
      .attr("fill", d => d3.schemeCategory10[d.index % 10])
      .on("click", clicked);
      
    group.append("text")
      .attr("x", d => d.x)
      .attr("y", d => d.y)            
      .style("fill", 'white')
      .text('a')
    

  function clicked(event, d) {
    if (event.defaultPrevented) return; // dragged

    d3.select(this).transition()
        .attr("fill", "black")
        .attr("r", radius * 2)
      .transition()
        .attr("r", radius)
        .attr("fill", d3.schemeCategory10[d.index % 10]);
  }


  function dragstarted() {
    d3.select(this).attr("stroke", "black");
  }

  function dragged(event, d) {
    d3.select(this).raise().attr("cx", d.x = event.x).attr("cy", d.y = event.y);
  }

  function dragended() {
    d3.select(this).attr("stroke", null);
  }
<script src="https://d3js.org/d3.v6.js"></script>
<div id="chart"></div>

标签: d3.jsclickdrag

解决方案


关键问题是您g使用cx,属性定位每个cy属性,并在拖动时更新这些属性。g元素不能通过cxor定位cy,因此最初设置这些元素并在拖动时更新它们不会导致任何变化。

您需要g使用翻译定位元素:

  .attr("transform", d=> "translate("+[d.x,d.y]+")" )   

此外,您现在不需要定位圆圈和文本 - 或者每个 x 和 y 坐标将应用两次 - 一次应用于g子元素,一次应用于子元素。

现在您只需要在每次拖动时更新翻译,例如:

function drag(event,d) {
    d.x = event.x;
    d.y = event.y;
    d3.select(this).raise().attr("transform", d=> "translate("+[d.x,d.y]+")" )
}

这应该给你类似的东西:

const width = 400;
  const height = 300;
  const radius = 5;
  
  const svg = d3.select("#chart").append('svg')
    .attr('width', '400')
    .attr('height', '400')
    .style('border', 'solid 1px');

  const circles = d3.range(20).map(i => ({
    x: Math.random() * (width - radius * 2) + radius,
    y: Math.random() * (height - radius * 2) + radius,
    index: i
  }));

const group =  svg.selectAll("g").data(circles).enter()
      .append("g")
      .attr("transform", d=> "translate("+[d.x,d.y]+")" )            
      .call(d3.drag()
      .on("start", dragstarted)
      .on("drag", dragged)
      .on("end", dragended))
  
  group.append("circle")    
      .attr("r", radius)
      .attr("fill", d => d3.schemeCategory10[d.index % 10])
      .on("click", clicked);
      
    group.append("text")    
      .style("fill", 'white')
      .text('a')
    

  function clicked(event, d) {
    if (event.defaultPrevented) return; // dragged

    d3.select(this).transition()
        .attr("fill", "black")
        .attr("r", radius * 2)
      .transition()
        .attr("r", radius)
        .attr("fill", d3.schemeCategory10[d.index % 10]);
  }


  function dragstarted() {
    d3.select(this).attr("stroke", "black");
  }

  function dragged(event, d) {
    d.x = event.x;
    d.y = event.y;
    d3.select(this).raise().attr("transform", d=> "translate("+[d.x,d.y]+")" )
  }

  function dragended() {
    d3.select(this).attr("stroke", null);
  }
<script src="https://d3js.org/d3.v6.js"></script>
<div id="chart"></div>


推荐阅读