Monday, 17 May 2021

d3.js dynamically append swarms with variable force bounds

I have a grid system that I'm using to display basic data:

var columns = 5;
var spacing = 250;
var vSpacing = 180;

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

And my simplified data set looks like:

var data = [
  {'bank':'bank1','q1':100,'q2':120,'products':d3.range(20).map(function(v) {return {name: v.toString()+'_fund'} })},
  {'bank':'bank2','q1':120,'q2':130,'products':d3.range(24).map(function(v) {return {name: v.toString()+'_fund'} })},
  {'bank':'bank3','q1':140,'q2':150,'products':d3.range(80).map(function(v) {return {name: v.toString()+'_fund'} })},
  {'bank':'bank4','q1':100,'q2':150,'products':d3.range(30).map(function(v) {return {name: v.toString()+'_fund'} })},
  {'bank':'bank5','q1':90,'q2':120,'products':d3.range(10).map(function(v) {return {name: v.toString()+'_fund'} })},
  {'bank':'bank6','q1':80,'q2':130,'products':d3.range(70).map(function(v) {return {name: v.toString()+'_fund'} })}
];

I've already appended rects and circles to form the scaffolding of the visual. Next I want to append swarm clusters around the circle that we see that juts off to the right-hand side of each rect. The idea is that each mini circle will represent one product; hence I'm trying to point the swarm cluster to the data in products. Also important is that each swarm is to assume initial force logic using the static right-hand circles I appended above; call them placeholders. Full snippet below:

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

var height = 450;
var width = 1100;

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 tsvData = d3.tsv('202105-xb.tsv');

//tsvData.then(function(rawData) {

//var data = rawData.map(function(d) {
//return {quota20:+d.quota20, quota21:+d.quota21, bank:d.bank, products:d3.range(+d.products).map(function(v) {return {name: v.toString()+'_fund'} })}
//});


var data = [
  {'bank':'bank1','q1':100,'q2':120,'products':d3.range(20).map(function(v) {return {name: v.toString()+'_fund'} })},
  {'bank':'bank2','q1':120,'q2':130,'products':d3.range(24).map(function(v) {return {name: v.toString()+'_fund'} })},
  {'bank':'bank3','q1':140,'q2':150,'products':d3.range(80).map(function(v) {return {name: v.toString()+'_fund'} })},
  {'bank':'bank4','q1':100,'q2':150,'products':d3.range(30).map(function(v) {return {name: v.toString()+'_fund'} })},
  {'bank':'bank5','q1':90,'q2':120,'products':d3.range(10).map(function(v) {return {name: v.toString()+'_fund'} })},
  {'bank':'bank6','q1':80,'q2':130,'products':d3.range(70).map(function(v) {return {name: v.toString()+'_fund'} })}
];

var rScale = d3.scaleLinear()
  .range([5, 25])
  .domain([0, 4000]);

var columns = 5;
var spacing = 250;
var vSpacing = 180;

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

bankG.append('rect')
.attr('x',0)
.attr('y',0)
.attr('width', 50)
.attr('height', 50)
.style('fill', "#003366");

bankG.append('line')
.attr('x1',50)
.attr('x2',90)
.attr('y1',25)
.attr('y2',25)
.style('stroke',"#a6a6a6");

bankG.append('circle')
.attr('cx', 90)
.attr('cy', 25)
.attr('r', 12)
.style('fill',"#003366");

var forceNodes = data.each().products;

forceNodes.push({fx:0,fy:0,placeholder:true});

var simulation = d3.forceSimulation(forceNodes)
.force("x", d3.forceX(function(d) {
return 20;
}).strength(0.2))
.force("y", d3.forceY(function(d) {
return 12.5;
}).strength(0.1))
.force("collide", d3.forceCollide().radius(function(d) {

  return d.placeholder ? 12 : 4;
}).iterations(1))
.stop();

simulation.tick(75);

   d3.select(this).selectAll(null)
.data(forceNodes.filter(function(d) { return !d.placeholder }))
.enter()
.append("circle")
.attr("r", 3)
.attr("cx", function(d) { return d.x;})
.attr("cy", function(d) { return d.y;})
.style('stroke',"none")
.style('fill', function(d) {return "#4f81b9"; })


//})
<script src="https://d3js.org/d3.v5.min.js"></script>

It seems I didn't quite access the data correctly for the swarm bit:

Uncaught TypeError: data.each is not a function

Question

Essentially, my question boils down to how do I get my swarm clusters to read the product data and append tightly around my existing normal circles?



from d3.js dynamically append swarms with variable force bounds

No comments:

Post a Comment