D3 Силовое моделирование в сетке - PullRequest
0 голосов
/ 09 мая 2018

Мне было интересно, как можно было бы изменить Пример Майка Бостока о раскладке нескольких сил , чтобы попытаться получить расклад сил для группировки узлов в сетке.

Итак, давайте представим, что у нас есть следующий 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 и не знаю, с чего начать. Пример, на который я указал, полезен, но трудно понять, что нужно изменить, поскольку в коде практически нет комментариев.

Любая помощь будет оценена.

1 Ответ

0 голосов
/ 09 мая 2018

Забудьте об этом примере: он использует 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 будет загружать ваши данные в виде строк, а не чисел. Измените это в соответствии с вашими потребностями.

...