【问题标题】:Bounding circle nodes within rectangle containers矩形容器内的边界圆节点
【发布时间】:2019-01-19 04:36:05
【问题描述】:

前几天我一直被这个问题困扰,但不幸的是无法找到解决方案。我正在尝试实现here 所示的行为,但通常根据每个节点的属性对各种容器执行此操作。我想联系并询问是否有任何已知的方法可以做到这一点。

下面是我的 JSFiddle 是我目前拥有的一个示例 - 分配给随机组号的多个节点和一个 barView 函数,该函数根据它们的组分隔这些节点。我希望将这些节点限制在它们各自条形的尺寸内,以便拖动这些节点无法将它们从盒子中移除,但它们可以在其中移动(相互反弹)。非常感谢您在这方面的帮助。

为简单起见,我在每个节点中制作了与“总计”字段相关的条(以显示 SVG 尺寸中的条),但这些条与我的实现中的大小相关,类似于卷。

我已经能够通过使用以下代码来组织节点的 x 位置,其中位置基于组:

simulation.force('x', d3.forceX().strength(1).x(function(d) {
    return xscale(d.group); // xvariable
}));

使用此代码,我不确定如何在矩形的尺寸范围内工作,或保持圆圈可以在其中反弹的边界。非常感谢您的帮助!

非常感谢!

我的小提琴:http://jsfiddle.net/abf2er7z/2/

【问题讨论】:

    标签: javascript d3.js bar-chart data-visualization force-layout


    【解决方案1】:

    一种可能的解决方案是设置一个新的tick 函数,它使用Math.maxMath.min 来获取这些矩形的边界:

    simulation.on("tick", function() {
        node.attr("cx", function(d) {
                return d.x = Math.min(Math.max(xscale(d.group) - 20 + d.radius, d.x), xscale(d.group) + 20 - d.radius);
            })
            .attr("cy", function(d) {
                return d.y = Math.min(Math.max(0.9 * height - heightMap[d.group] + d.radius, d.y), height - d.radius);
            });
    });
    

    这里是演示:

    var width = 900,
      height = 400;
    
    var groupList = ['Group A', 'Group B', 'Group C', 'Group D'];
    var data = d3.range(200).map(d => ({
      id: d,
      group: groupList[getRandomIntegerInRange(0, 3)],
      size: getRandomIntegerInRange(1, 100),
      total: getRandomIntegerInRange(1, 10)
    }))
    
    var svg = d3.select("body")
      .append("svg")
      .attr("viewBox", "0 0 " + (width) + " " + (height))
      .attr("preserveAspectRatio", "xMidYMid meet")
      .attr('width', "100%")
      .attr('height', height)
      .attr('id', 'svg')
      .append('g')
      .attr('id', 'container')
      .attr('transform', 'translate(' + 0 + ', ' + 0 + ')');
    
    simulation = d3.forceSimulation();
    
    data.forEach(function(d, i) {
      d.radius = Math.sqrt(d['size']);
    });
    
    colorScale = d3.scaleOrdinal(d3.schemeCategory10);
    
    node = svg.append("g")
      .attr("class", "node")
      .selectAll(".bubble")
      .data(data, function(d) {
        return d.id;
      })
      .enter().append("circle")
      .attr('class', 'bubble')
      .attr('r', function(d) {
        return d.radius;
      }) // INITIALIZED RADII TO 0 HERE
      .attr("fill", function(d) {
        // initially sets node colors
        return colorScale(d.group);
      })
      .attr('stroke-width', 0.5)
      .call(d3.drag()
        .on("start", dragstarted)
        .on("drag", dragged)
        .on("end", dragended));
    
    function dragstarted(d) {
      if (!d3.event.active) {
        simulation.alpha(.07).restart()
      }
    
      d.fx = d.x;
      d.fy = d.y;
    }
    
    function dragged(d) {
      d.fx = d3.event.x;
      d.fy = d3.event.y;
    }
    
    function dragended(d) {
      if (!d3.event.active) simulation.alpha(0.07).restart()
    
      d.fx = null;
      d.fy = null;
      // Update and restart the simulation.
      simulation.nodes(data);
    }
    
    simulation
      .nodes(data)
      .force("x", d3.forceX().strength(0.1).x(width / 2))
      .force("y", d3.forceY().strength(0.1).y(height / 2))
      .force("collide", d3.forceCollide().strength(0.7).radius(function(d) {
        return d.radius + 0.5;
      }).iterations(2))
      .on("tick", function() {
        node
          .attr("cx", function(d) {
            return d.x;
          })
          .attr("cy", function(d) {
            return d.y;
          });
      });
    
    function barView() {
      var buff = width * 0.12
    
      var leftBuff = buff;
      var rightBuff = width - buff;
    
      var scale;
    
      xscale = d3.scalePoint()
        .padding(0.1)
        .domain(groupList)
        .range([leftBuff, rightBuff]);
    
    
      // Save double computation below.
      heightMap = {}
      groupList.forEach(function(d) {
        currVarTotal = data.filter(function(n) {
          return n.group === d;
        }).reduce(function(a, b) {
          return a + +b.total;
        }, 0);
        heightMap[d] = currVarTotal;
      })
    
    
      var rects = svg.selectAll('.rect')
        .data(groupList)
        .enter()
        .append('rect')
        .attr('x', function(d) {
          return xscale(d) - 20
        })
        .attr('y', function(d) {
          return 0.9 * height - heightMap[d];
        })
        .attr('width', 40)
        .attr('height', function(d) {
          return heightMap[d];
        })
        .attr('fill', 'transparent')
        .attr('stroke', function(d) {
          return colorScale(d)
        })
        .attr('stroke-width', 2)
        .attr('class', 'chartbars');
    
      drawTheAxis(xscale);
    
      simulation.force('x', d3.forceX().strength(1).x(function(d) {
        return xscale(d.group); // xvariable
      })).on("tick", function() {
        node
          .attr("cx", function(d) {
            return d.x = Math.min(Math.max(xscale(d.group) - 20 + d.radius, d.x), xscale(d.group) + 20 - d.radius);
          })
          .attr("cy", function(d) {
            return d.y = Math.min(Math.max(0.9 * height - heightMap[d.group] + d.radius, d.y), height - d.radius);
          });
      });
    
      currHeights = {}
      Object.keys(heightMap).forEach(d => {
        currHeights[d] = 0.9 * height
      });
    
      // restart the simulation
      simulation.alpha(0.07).restart();
    
      function drawTheAxis(scale) {
    
        var bottomBuffer = 0.9 * height;
        // create axis objects
        var xAxis = d3.axisBottom(xscale);
    
        // Draw Axis
        var gX = svg.append("g") // old: nodeG.append
          .attr("class", "xaxis")
          .attr('stroke-width', 2)
          .attr("transform", "translate(0," + height + ")")
          .attr('opacity', 0)
          .call(xAxis)
          .transition()
          .duration(250)
          .attr('opacity', 1)
          .attr("transform", "translate(0," + bottomBuffer + ")");
      }
    }
    
    function getRandomIntegerInRange(min, max) {
      return Math.floor(Math.random() * (Math.floor(max) - Math.ceil(min) + 1)) + Math.ceil(min);
    }
    
    setTimeout(function() {
      barView();
    }, 1500);
    <script src="https://d3js.org/d3.v5.min.js"></script>

    请记住,这不是最终解决方案,而只是一般指导:过渡、数学(使用那些神奇的数字)和音阶需要改进。

    【讨论】:

      猜你喜欢
      • 2011-08-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-02-22
      • 2015-06-10
      • 1970-01-01
      • 2014-06-16
      • 2014-09-22
      相关资源
      最近更新 更多