D3.js модульные кластеры роя версии 5 (с переменным радиусом?)

#javascript #d3.js

#javascript #d3.js

Вопрос:

Я хочу создать визуализацию, в которой рой содержит один большой круг и множество окружностей-спутников, цепляющихся за него. Для простой демонстрации я подготовил небольшую версию набора данных; каждый элемент в массиве должен иметь один большой круг, а затем, как бы много меньших кругов к нему не прилипало:

 var data = [
  {'wfoe':'wfoe1','products':d3.range(20)},
  {'wfoe':'wfoe2','products':d3.range(40)},
  {'wfoe':'wfoe3','products':d3.range(10)}
];
  

Вот фрагмент моего прогресса:

 var margins = {
  top: 100,
  bottom: 300,
  left: 100,
  right: 100
};

var height = 250;
var width = 900;

var totalWidth = width   margins.left   margins.right;
var totalHeight = height   margins.top   margins.bottom;

var svg = d3.select('body')
  .append('svg')
  .attr('width', totalWidth)
  .attr('height', totalHeight);

var graphGroup = svg.append('g')
  .attr('transform', "translate("   margins.left   ","   margins.top   ")");



var data = [
  {'wfoe':'wfoe1','products':d3.range(20)},
  {'wfoe':'wfoe2','products':d3.range(40)},
  {'wfoe':'wfoe3','products':d3.range(10)}
];

var columns = 4;
var spacing = 250;
var vSpacing = 250;

var fmcG = graphGroup.selectAll('.fmc')
  .data(data)
  .enter()
  .append('g')
  .attr('class', 'fmc')
  .attr('id', (d, i) => 'fmc'   i)
  .attr('transform', (d, k) => {
var horSpace = (k % columns) * spacing;
var vertSpace = ~~((k / columns)) * vSpacing;
return "translate("   horSpace   ","   vertSpace   ")";
  });

var xScale = d3.scalePoint()
  .range([0, width])
  .domain([0, 100]);

var rScale = d3.scaleThreshold()
  .range([50,5])
  .domain([0,1]);

data.forEach(function(d, i) {
  d.x = (i % columns) * spacing;
  d.y = ~~((i / columns)) * vSpacing;
});


var simulation = d3.forceSimulation(data)
  .force("x", d3.forceX(function(d,i) {
return (i % columns) * spacing;
  }).strength(0.1))
  .force("y", d3.forceY(function(d,i) {
return ~~((i / columns)) * vSpacing;
  }).strength(0.01))
  .force("collide", d3.forceCollide(function(d,i) { return rScale(i)}))
  .stop();

simulation.tick(75);

fmcG.selectAll(null)
  .data(data)
  .enter()
  .append("circle")
  .attr("r", function(d,i) {
return rScale(i)
  })
  .attr("cx", function(d) {
return d.x;
  })
  .attr("cy", function(d) {
return d.y;
  })
  .style('fill',"#003366");  
 <script src="https://d3js.org/d3.v5.min.js"></script>  

Я хочу быстро указать, что большой круг не представляет никакой точки данных (они просто собираются разместить имя / логотип). Я просто подумал, что включение его в данные моделирования было бы самым простым способом ввести необходимую силовую логику для кругов роя. Я подумал, что элегантным решением было бы использовать пороговую шкалу и позволить первой (i = 0) исходной точке всегда быть самой большой окружностью. Вот что я имею в виду:

 var rScale = d3.scaleThreshold()
  .range([0, 1])
  .domain([50, 5]);

fmcG.selectAll(null)
  .data(data)
  .enter()
  .append("circle")
  .attr("r", function(d,i) {
    return rScale(i)
  })
  .attr("cx", function(d) {
    return d.x;
  })
  .attr("cy", function(d) {
    return d.y;
  })
  .style('fill',"#003366");
  

Результат, о котором я упоминал выше (три больших круга с маленькими кругами вокруг них), не был достигнут, и на самом деле было добавлено очень мало кругов, и компонент с переменным радиусом, похоже, работал не так, как я думал. (также в журнале не отображаются ошибки).

Вопрос

Как я могу итеративно создавать рои, которые начинаются с одного большого круга и добавляют последующие меньшие круги вокруг начального большого круга, в зависимости от образца набора данных?

Ответ №1:

Вы могли бы использовать моделирование силы, как показано ниже, только это дает недетерминированные результаты. Однако это действительно хорошо, когда вы хотите постепенно добавлять больше узлов. В приведенном ниже решении я дал всем связанным узлам ссылку на центральный узел, но не нарисовал его. Это позволило связанным узлам сильно привлекать.

С другой стороны, вы также можете использовать bubble chart , если хотите, чтобы D3 нашел оптимальное для вас решение для упаковки, без принудительного воздействия на них. Единственным недостатком является то, что вам придется каждый раз вызывать функцию упаковки со всеми узлами, а другие узлы могут смещаться из-за нового.

 var margins = {
  top: 100,
  bottom: 300,
  left: 100,
  right: 100
};

var height = 250;
var width = 900;

var totalWidth = width   margins.left   margins.right;
var totalHeight = height   margins.top   margins.bottom;

var svg = d3.select('body')
  .append('svg')
  .attr('width', totalWidth)
  .attr('height', totalHeight);

var graphGroup = svg.append('g')
  .attr('transform', "translate("   margins.left   ","   margins.top   ")");

var data = [{
    'wfoe': 'wfoe1',
    'products': d3.range(20).map(function(v) {
      return v.toString()   '_wfoe1';
    })
  },
  {
    'wfoe': 'wfoe2',
    'products': d3.range(40).map(function(v) {
      return v.toString()   '_wfoe2';
    })
  },
  {
    'wfoe': 'wfoe3',
    'products': d3.range(10).map(function(v) {
      return v.toString()   '_wfoe3';
    })
  }
];

var columns = 4;
var spacing = 250;
var vSpacing = 250;

function dataToNodesAndLinks(d) {
  // Create one giant array of points and
  // one link between each wfoe and each product
  var nodes = [{
    id: d.wfoe,
    center: true
  }];
  var links = [];

  d.products.forEach(function(p) {
    nodes.push({
      id: p,
      center: false
    });
    links.push({
      source: d.wfoe,
      target: p
    });
  });
  return {
    nodes: nodes,
    links: links
  };
}

var fmcG = graphGroup.selectAll('.fmc')
  .data(data.map(function(d, i) {
    return dataToNodesAndLinks(d, i);
  }))
  .enter()
  .append('g')
  .attr('class', 'fmc')
  .attr('id', (d, i) => 'fmc'   i)
  .attr('transform', (d, k) => {
    var horSpace = (k % columns) * spacing;
    var vertSpace = ~~((k / columns)) * vSpacing;
    return "translate("   horSpace   ","   vertSpace   ")";
  });

var xScale = d3.scalePoint()
  .range([0, width])
  .domain([0, 100]);

var rScale = d3.scaleThreshold()
  .range([50, 5])
  .domain([0, 1]);

fmcG.selectAll("circle")
  .data(function(d) {
    return d.nodes;
  })
  .enter()
  .append("circle")
  .attr("id", function(d) {
    return d.id;
  })
  .attr("r", function(d, i) {
    return d.center ? rScale(i) * 5 : rScale(i);
  })
  .style('fill', function(d) { return d.center ? "darkred" : "#003366"; })

fmcG
  .each(function(d, i) {
    d3.forceSimulation(d.nodes)
      .force("collision", d3.forceCollide(function(d) {
        return d.center ? rScale(i) * 5 : rScale(i);
      }))
      .force("center", d3.forceCenter(0, 0))
      .force("link", d3
        .forceLink(d.links)
        .id(function(d) {
          return d.id;
        })
        .distance(0)
        .strength(2))
      .on('tick', ticked);
  });

function ticked() {
  fmcG.selectAll("circle")
    .attr("transform", function(d) {
      return "translate("   d.x   ","   d.y   ")";
    });
}  
 <script src="https://d3js.org/d3.v5.js"></script>  

Комментарии:

1. Очень круто, спасибо! Просто интересно, как отключить анимацию ввода для моделирования — есть ли простой способ?