Circle packing with static size

I'm trying to implement the D3 pack layout with static size circle values where I want D3 to take care of just placement but I see the d3 is overriding the provided circle size. I'm not sure how to keep the same size passed from the data. See the below code, in that, when I change the size of the children with label RAD from 100 to 5000, I see the size of other circles are changing, how can I make the layout to render with the exact size which I get from data? please provide me some pointer or jsfiddle

link to codepen - https://codepen.io/navinleon/pen/mxZJWr

Thanks in advance.

var w = 1000,
    h = 500;

var data = {
    name: "root",
    children: [{
        label: 'RAD',
        size: 100,
        color: '#c99700'
    }, {
        label: 'BIL',
        size: 100,
        color: '#008ce6'
    }, {
        label: 'EEN',
        size: 100,
        color: '#007377'
    }, {
        label: 'INO',
        size: 100,
        color: '#b4975a'
    }, ]
};

var canvas = d3.select("#canvas")
    .append("svg:svg")
    .attr('width', w)
    .attr('height', h);

var nodes = d3.layout.pack()
    .value(function (d) {
        return d.size;
    }).padding(100)
    .size([w, h])
    .nodes(data);

// Get rid of root node
nodes.shift();

canvas.selectAll('circles')
    .data(nodes)
    .enter()
    .append('svg:circle')
    .attr('cx', function (d) {
        return d.x;
    })
    .attr('cy', function (d) {
        return d.y;
    })
    .attr('r', function (d) {
        return d.r;
    })
    .attr('fill', function (d) {
        return d.color;
    });

Answers:

Answer

Using d3.packSiblings():

There is in fact a convenient method, named d3.packSiblings, which seems to be exactly what you need (without any force simulation) to position the nodes.

According to the API:

Packs the specified array of circles, each of which must have a circle.r property specifying the circle’s radius.

So, changing the size property in your data to r, all you need is something like:

var packed = d3.packSiblings(data.children);

Here is the demo:

var data = {
  name: "root",
  children: [{
    label: 'RAD',
    r: 50,
    color: '#c99700'
  }, {
    label: 'BIL',
    r: 75,
    color: '#008ce6'
  }, {
    label: 'EEN',
    r: 20,
    color: '#007377'
  }, {
    label: 'INO',
    r: 42,
    color: '#b4975a'
  }, ]
};

var packed = d3.packSiblings(data.children);

var w = 500,
  h = 400;
var svg = d3.select("body")
  .append("svg")
  .attr("width", w)
  .attr("height", h)
  .append("g")
  .attr("transform", "translate(" + w / 2 + "," + h / 2 + ")");

var color = d3.scaleOrdinal(d3.schemeCategory10);

var nodes = svg.selectAll(null)
  .data(data.children)
  .enter()
  .append("circle")
  .attr("cx", function(d) {
    return d.x
  })
  .attr("cy", function(d) {
    return d.y
  })
  .attr("r", function(d) {
    return d.r
  })
  .style("fill", function(_, i) {
    return color(i)
  })
<script src="https://d3js.org/d3.v5.min.js"></script>

Using a force simulation

You actually don't want a circle pack layout. The circle pack layout will necessarily fit all the circles in the area specified by pack.size array.

Since you want to set the circles' size yourself and you want D3 to only take care of placement, the most obvious option that comes to my mind is using a force simulation instead.

In this proposed solution, we'll set the focus point to the center of the SVG using forceX and forceY. Then, we'll stop the simulation and run it a given number of times (the less elements you have the less iterations you need):

var simulation = d3.forceSimulation(data.children)
  .force("x", d3.forceX(w / 2))
  .force("y", d3.forceY(h / 2))
  .force("collide", d3.forceCollide(function(d) {
    return d.size
  }))
  .stop();

Here are some demos.

First, setting all circles' radiuses to 100:

var w = 500,
  h = 400;
var svg = d3.select("body")
  .append("svg")
  .attr("width", w)
  .attr("height", h);

var color = d3.scaleOrdinal(d3.schemeCategory10)

var data = {
  name: "root",
  children: [{
    label: 'RAD',
    size: 100,
    color: '#c99700'
  }, {
    label: 'BIL',
    size: 100,
    color: '#008ce6'
  }, {
    label: 'EEN',
    size: 100,
    color: '#007377'
  }, {
    label: 'INO',
    size: 100,
    color: '#b4975a'
  }, ]
};

var simulation = d3.forceSimulation(data.children)
  .force("x", d3.forceX(w / 2))
  .force("y", d3.forceY(h / 2))
  .force("collide", d3.forceCollide(function(d) {
    return d.size
  }))
  .stop();

for (var i = 0; i < 100; ++i) simulation.tick();

var nodes = svg.selectAll(null)
  .data(data.children)
  .enter()
  .append("circle")
  .attr("cx", function(d) {
    return d.x
  })
  .attr("cy", function(d) {
    return d.y
  })
  .attr("r", function(d) {
    return d.size
  })
  .style("fill", function(_, i) {
    return color(i)
  })
<script src="https://d3js.org/d3.v5.min.js"></script>

Now let's reduce the size of 3 circles to 20:

var w = 500,
  h = 400;
var svg = d3.select("body")
  .append("svg")
  .attr("width", w)
  .attr("height", h);

var color = d3.scaleOrdinal(d3.schemeCategory10)

var data = {
  name: "root",
  children: [{
    label: 'RAD',
    size: 20,
    color: '#c99700'
  }, {
    label: 'BIL',
    size: 100,
    color: '#008ce6'
  }, {
    label: 'EEN',
    size: 20,
    color: '#007377'
  }, {
    label: 'INO',
    size: 20,
    color: '#b4975a'
  }, ]
};

var simulation = d3.forceSimulation(data.children)
  .force("x", d3.forceX(w / 2))
  .force("y", d3.forceY(h / 2))
  .force("collide", d3.forceCollide(function(d) {
    return d.size
  }))
  .stop();

for (var i = 0; i < 100; ++i) simulation.tick();

var nodes = svg.selectAll(null)
  .data(data.children)
  .enter()
  .append("circle")
  .attr("cx", function(d) {
    return d.x
  })
  .attr("cy", function(d) {
    return d.y
  })
  .attr("r", function(d) {
    return d.size
  })
  .style("fill", function(_, i) {
    return color(i)
  })
<script src="https://d3js.org/d3.v5.min.js"></script>

Now all circles with 10 as size:

var w = 500,
  h = 400;
var svg = d3.select("body")
  .append("svg")
  .attr("width", w)
  .attr("height", h);

var color = d3.scaleOrdinal(d3.schemeCategory10)

var data = {
  name: "root",
  children: [{
    label: 'RAD',
    size: 10,
    color: '#c99700'
  }, {
    label: 'BIL',
    size: 10,
    color: '#008ce6'
  }, {
    label: 'EEN',
    size: 10,
    color: '#007377'
  }, {
    label: 'INO',
    size: 10,
    color: '#b4975a'
  }, ]
};

var simulation = d3.forceSimulation(data.children)
  .force("x", d3.forceX(w / 2))
  .force("y", d3.forceY(h / 2))
  .force("collide", d3.forceCollide(function(d) {
    return d.size
  }))
  .stop();

for (var i = 0; i < 100; ++i) simulation.tick();

var nodes = svg.selectAll(null)
  .data(data.children)
  .enter()
  .append("circle")
  .attr("cx", function(d) {
    return d.x
  })
  .attr("cy", function(d) {
    return d.y
  })
  .attr("r", function(d) {
    return d.size
  })
  .style("fill", function(_, i) {
    return color(i)
  })
<script src="https://d3js.org/d3.v5.min.js"></script>

Finally, let's make one of the circles 5000. The only thing you'll see is the whole SVG as a single color... but this is what you asked for: the circle has a radius of 5000 pixels. Check it:

var w = 500,
  h = 400;
var svg = d3.select("body")
  .append("svg")
  .attr("width", w)
  .attr("height", h);

var color = d3.scaleOrdinal(d3.schemeCategory10)

var data = {
  name: "root",
  children: [{
    label: 'RAD',
    size: 5000,
    color: '#c99700'
  }, {
    label: 'BIL',
    size: 100,
    color: '#008ce6'
  }, {
    label: 'EEN',
    size: 100,
    color: '#007377'
  }, {
    label: 'INO',
    size: 100,
    color: '#b4975a'
  }, ]
};

var simulation = d3.forceSimulation(data.children)
  .force("x", d3.forceX(w / 2))
  .force("y", d3.forceY(h / 2))
  .force("collide", d3.forceCollide(function(d) {
    return d.size
  }))
  .stop();

for (var i = 0; i < 100; ++i) simulation.tick();

var nodes = svg.selectAll(null)
  .data(data.children)
  .enter()
  .append("circle")
  .attr("cx", function(d) {
    return d.x
  })
  .attr("cy", function(d) {
    return d.y
  })
  .attr("r", function(d) {
    return d.size
  })
  .style("fill", function(_, i) {
    return color(i)
  })
<script src="https://d3js.org/d3.v5.min.js"></script>

Answer

Make the attribute 'r' return the size of each data node like this:

.attr('r', function (d) {
    return d.size;
})

I'm not sure which attribute of the circle you want defined from your size, but you can modify accordingly. Right now, the radii are set as 150, 120, 100

Now your data will change the size of circle. You can try with this data:

var data = {
    name: "root",
    children: [{
        label: 'RAD',
        size: 150,
        color: '#c99700'
    }, {
        label: 'BIL',
        size: 120,
        color: '#008ce6'
    }, {
        label: 'EEN',
        size: 100,
        color: '#007377'
    }]
};

See codepen: https://codepen.io/andrewgi/pen/xWvNJe

Tags

Recent Questions

Top Questions

Home Tags Terms of Service Privacy Policy DMCA Contact Us

©2020 All rights reserved.