vendredi 11 septembre 2015

Animate objects in force layout in D3.js

I need to create a data visualisation which would look like a bunch of floating bubbles with text inside of the bubble.

I have a partially working example which uses mock data prepared here: JSfiddle

// helpers
var random = function(min, max) {
    if (max == null) {
        max = min;
        min = 0;
    }
    return min + Math.floor(Math.random() * (max - min + 1));
};

// mock data
var colors = [
    {
        fill: 'rgba(242,216,28,0.3)',
        stroke: 'rgba(242,216,28,1)'
    },
    {
        fill: 'rgba(207,203,196,0.3)',
        stroke: 'rgba(207,203,196,1)'
    },
    {
        fill: 'rgba(0,0,0,0.2)',
        stroke: 'rgba(100,100,100,1)'
    }
];
var data = [];
for(var j = 0; j <= 2; j++) {
    for(var i = 0; i <= 4; i++) {
        var text = 'text' + i;
        var category = 'category' + j;
        var r = random(50, 100);
        data.push({
            text: text,
            category: category,
            r: r,
            r_change_1: r + random(-20, 20),
            r_change_2:  r + random(-20, 20),
            fill: colors[j].fill,
            stroke: colors[j].stroke
        });
    }
}
// mock debug
//console.table(data);

// collision detection
// derived from http://ift.tt/1MgMORF
function collide(alpha) {
    var quadtree = d3.geom.quadtree(data);
    return function(d) {
        var r = d.r + 10,
            nx1 = d.x - r,
            nx2 = d.x + r,
            ny1 = d.y - r,
            ny2 = d.y + r;
        quadtree.visit(function(quad, x1, y1, x2, y2) {
            if (quad.point && (quad.point !== d)) {
                var x = d.x - quad.point.x,
                    y = d.y - quad.point.y,
                    l = Math.sqrt(x * x + y * y),
                    r = d.r * 2;
                if (l < r) {
                    l = (l - r) / l * alpha;
                    d.x -= x *= l;
                    d.y -= y *= l;
                    quad.point.x += x;
                    quad.point.y += y;
                }
            }
            return x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1;
        });
    };
}

// initialize
var container = d3.select('.bubble-cloud');
var $container = $('.bubble-cloud');
var containerWidth = $container.width();
var containerHeight = $container.height();
var svgContainer = container
    .append('svg')
    .attr('width', containerWidth)
    .attr('height', containerHeight);

// prepare layout
var force = d3.layout
    .force()
    .size([containerWidth, containerHeight])
    .gravity(0)
    .charge(0)
;

// load data
force.nodes(data)
    .start()
;

// create item groups
var node = svgContainer.selectAll('.node')
    .data(data)
    .enter()
    .append('g')
    .attr('class', 'node')
    .call(force.drag);

// create circles
node.append('circle')
    .classed('circle', true)
    .attr('r', function (d) {
            return d.r;
        })
    .style('fill', function (d) {
            return d.fill;
        })
    .style('stroke', function (d) {
        return d.stroke;
    });

// create labels
node.append('text')
    .text(function(d) {
        return d.text
    })
    .classed('text', true)
    .style({
        'fill': '#ffffff',
        'text-anchor': 'middle',
        'font-size': '12px',
        'font-weight': 'bold',
        'font-family': 'Tahoma, Arial, sans-serif'
    })
;

node.append('text')
    .text(function(d) {
        return d.category
    })
    .classed('category', true)
    .style({
        'fill': '#ffffff',
        'font-family': 'Tahoma, Arial, sans-serif',
        'text-anchor': 'middle',
        'font-size': '9px'
    })
;

node.append('line')
    .classed('line', true)
    .attr('x1', 0)
    .attr('y1', 0)
    .attr('x2', 50)
    .attr('y2', 0)
    .attr('stroke-width', 1)
    .attr('stroke',  function (d) {
        return d.stroke;
    })
;

// put circle into movement
force.on('tick', function(){

    d3.selectAll('circle')
        .each(collide(.5))
        .attr('cx', function (d) {

            // boundaries
            if(d.x <= d.r) {
                d.x = d.r + 1;
            }
            if(d.x >= containerWidth - d.r) {
                d.x = containerWidth - d.r - 1;
            }
            return d.x;
        })
        .attr('cy', function (d) {

            // boundaries
            if(d.y <= d.r) {
                d.y = d.r + 1;
            }
            if(d.y >= containerHeight - d.r) {
                d.y = containerHeight - d.r - 1;
            }
            return d.y;
        });

    d3.selectAll('line')
        .attr('x1', function (d) {
            return d.x - d.r + 10;
        })
        .attr('y1', function (d) {
            return d.y;
        })
        .attr('x2', function (d) {
            return d.x + d.r - 10;
        })
        .attr('y2', function (d) {
            return d.y;
        });

    d3.selectAll('.text')
        .attr('x', function (d) {
            return d.x;
        })
        .attr('y', function (d) {
            return d.y - 10;
        });

    d3.selectAll('.category')
        .attr('x', function (d) {
            return d.x;
        })
        .attr('y', function (d) {
            return d.y + 20;
        });
});

// animate
var interval = setInterval(function(){

    // moving of the circles
    // ...

}, 5 * 1000);

However I am now facing problem with animation. I cannot figure out how can I animate nodes in force diagram. I tried to adjust values of the data object and then invoke .tick() method inside setInterval method, however it didn't help. I am utilizing D3 force layout.

My questions are:

  • How to make the bubbles "float" around the screen, i.e. how to animate them?

  • How to animate changes of circle radius?

Thank you for your ideas.



via Chebli Mohamed

Aucun commentaire:

Enregistrer un commentaire