Reputation: 2617
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
Essentially, I'm trying to figure out how I get my swarm clusters to read the product
data and append tightly around my existing normal circles? (presumably requiring variable force logic like in my attempt)
Upvotes: 3
Views: 80
Reputation: 38151
I'm not sure if I'm interpreting quite correctly, but if so, a solution could look like this:
You have nested data, the parent g
, line, and rectangle have a different datum than the children in the force layout. So first we append the parent g and the line and rectangle contained by it. I'm not appending what I understand to be the placeholder circle node as we can do that with the rest, otherwise, this is the same as before.
Now we can apply a force layout to the nodes contained in the products
property:
bankG.each(function(bank) {
bank.products.push({fx:0,fy:0,placeholder:true})
var simulation = d3.forceSimulation(bank.products)
.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);
})
With the products array updated with positional data based on the force layout, we can now do our nested enter. To do so we use the products
property of the each parent g
as the data array for the selection of children:
bankG.selectAll(null)
.data(function(d) { return d.products; })
.enter()
.append("circle")
.attr("r", function(d) {
...
Here's a quick example:
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');
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");
// For each bankG:
bankG.each(function(bank) {
bank.products.push({fx:0,fy:0,placeholder:true})
var simulation = d3.forceSimulation(bank.products)
.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);
})
bankG.selectAll(null)
.data(function(d) { return d.products; })
.enter()
.append("circle")
.attr("r", function(d) {
return d.placeholder ? 12 : 3;
})
.attr("cx", function(d) { return d.x + 90})
.attr("cy", function(d) { return d.y + 25})
.style('stroke',"none")
.style('fill', function(d) {
return d.placeholder ? "#003366" : "#4f81b9";
})
//})
<script src="https://d3js.org/d3.v5.min.js"></script>
Upvotes: 1