【问题标题】:D3 Force simulation in a gridD3 网格中的力模拟
【发布时间】:2018-10-19 13:32:48
【问题描述】:

我想知道如何修改Mike Bostock's example of a multi-force layout 以尝试强制布局以对网格中的节点进行分组。

让我们假设我们有以下 csv:

Name, Category1, Category2
1,1,1
2,1,2
3,1,1
4,2,2
5,3,1
6,1,4
7,5,5
8,1,5
9,2,4
10,3,3
11,4,4
12,4,5
13,3,4
14,1,2
15,1,1
16,2,2
17,3,1
18,2,1
19,4,5
20,3,1

对于他的数据类型,我希望将类别 1 的所有可能值作为列,将类别 2 的所有可能值作为行,并希望我的节点根据它们的值自动分组在“正确”单元格中用于 1 类和 2 类。

我刚刚开始使用 D3,并不知道从哪里开始。我指出的示例很有用,但很难知道要修改什么,因为代码几乎没有 cmets。

任何帮助将不胜感激。

【问题讨论】:

    标签: javascript d3.js force-layout


    【解决方案1】:

    忘记那个例子:它使用 D3 v3,这使得定位节点的方式更加复杂。

    在 D3 v4/v5 中有两种方便的方法,forceX 和 forceY。

    您需要做的就是创建您的量表,例如使用点量表(我认为这是最好的选择):

    var columnScale = d3.scalePoint()
      .domain(["1", "2", "3", "4", "5"])
      .range([min, max]);
    
    var rowScale = d3.scalePoint()
      .domain(["1", "2", "3", "4", "5"])
      .range([min, max]);
    

    然后在模拟中使用这些比例:

    var simulation = d3.forceSimulation(data)
      .force("x", d3.forceX(function(d) {
        return columnScale(d.Category1)
      }))
      .force("y", d3.forceY(function(d) {
        return rowScale(d.Category2)
      }))
    

    这是一个基本演示,其中包含您共享的数据(我使用色标来突出显示网格上的不同位置):

    var csv = `Name,Category1,Category2
    1,1,1
    2,1,2
    3,1,1
    4,2,2
    5,3,1
    6,1,4
    7,5,5
    8,1,5
    9,2,4
    10,3,3
    11,4,4
    12,4,5
    13,3,4
    14,1,2
    15,1,1
    16,2,2
    17,3,1
    18,2,1
    19,4,5
    20,3,1`;
    
    var data = d3.csvParse(csv);
    
    var w = 250,
      h = 250;
    
    var svg = d3.select("body")
      .append("svg")
      .attr("width", w)
      .attr("height", h);
    
    var color = d3.scaleOrdinal(d3.schemeCategory10);
    
    var columnScale = d3.scalePoint()
      .domain(dataRange(data, 'Category1')) // or ["1", "2", "3", "4", "5"]
      .range([30, w - 10])
      .padding(0.5);
    
    var rowScale = d3.scalePoint()
      .domain(dataRange(data, 'Category2')) // or ["1", "2", "3", "4", "5"]
      .range([30, h - 10])
      .padding(0.5);
    
    var simulation = d3.forceSimulation(data)
      .force("x", d3.forceX(function(d) {
        return columnScale(d.Category1)
      }))
      .force("y", d3.forceY(function(d) {
        return rowScale(d.Category2)
      }))
      .force("collide", d3.forceCollide(6))
    
    var nodes = svg.selectAll(null)
      .data(data)
      .enter()
      .append("circle")
      .attr("r", 5)
      .attr("fill", function(d) {
        return color(d.Category1 + d.Category2)
      });
    
    var xAxis = d3.axisTop(columnScale)(svg.append("g").attr("transform", "translate(0,30)"));
    
    var yAxis = d3.axisLeft(rowScale)(svg.append("g").attr("transform", "translate(30,0)"));
    
    simulation.on("tick", function() {
      nodes.attr("cx", function(d) {
          return d.x
        })
        .attr("cy", function(d) {
          return d.y
        })
    });
    
    function dataRange(records, field) {
      var min = d3.min(records.map(record => parseInt(record[field], 10)));
      var max = d3.max(records.map(record => parseInt(record[field], 10)));
      return d3.range(min, max + 1);
    };
    svg {
      background-color: floralwhite;
      border: 1px solid gray;
    }
    <script src="https://d3js.org/d3.v5.min.js"></script>

    PS:在这两种尺度中,我都在域中使用字符串,因为d3.csv 会将您的数据加载为字符串,而不是数字。根据您的需要进行更改。

    【讨论】:

    • 如果您不介意,我在其中添加了一些便利函数来计算每个类别的最大值范围...
    • 谢谢,@Mr.Polywhirl,我真的很感激,但是 D3 已经有了这些功能,即 d3.range 和 d3.max,它们在 D3 代码中相当常见。我没有使用它们,因为 OP 将字符串作为值,而不是数字(我看到您的 maxValue 函数将字符串转换为数字),这就是我将文字数组留在域中的原因。
    • 我使用了内置的 d3 函数,我添加了 cmets 说明可以在那里使用字符串范围,但其他方面是相同的。
    • @Mr.Polywhirl 太好了,再次感谢。
    • 精彩的答案!很好的解释和完全模块化。感谢那。投票并标记为已接受:)
    猜你喜欢
    • 1970-01-01
    • 2017-02-25
    • 2022-01-14
    • 2014-02-27
    • 2014-10-27
    • 2013-04-13
    • 2019-06-18
    • 2022-11-28
    • 1970-01-01
    相关资源
    最近更新 更多