1
votes

I'm developing a simple animation using d3.js library and show it through canvas, but my canvas isn't responsive.

In the beginning of the code, I set original width to 960 and height to 500, but how can I make it resizable (responsive)? I'm not using CSS to apply some style (width and height) to the canvas, I'm using JavaScript to do it. The solution can be CSS or JavaScript.

var width = 960,
    height = 500,
    τ = 2 * Math.PI,
    gravity = .05;

var sample = poissonDiscSampler(width, height, 30),
    nodes = [{
        x: 0,
        y: 0
    }],
    s;

while (s = sample()) nodes.push(s);

var force = d3.layout.force()
    .size([width, height])
    .nodes(nodes.slice())
    .gravity(0)
    .charge(function(d, i) {
        return i ? -30 : -3000;
    })
    .on("tick", ticked)
    .start();

var voronoi = d3.geom.voronoi()
    .x(function(d) {
        return d.x;
    })
    .y(function(d) {
        return d.y;
    });

var root = nodes.shift();

root.fixed = true;

var links = voronoi.links(nodes);

var canvas = d3.select("#canvas").append("canvas")
    .attr("width", width)
    .attr("height", height)
    .on("ontouchstart" in document ? "touchmove" : "mousemove", moved);

var context = canvas.node().getContext("2d");



function moved() {
    var p1 = d3.mouse(this);
    root.px = p1[0];
    root.py = p1[1];
    force.resume();
}

function ticked() {
    force.resume();

    for (var i = 0, n = nodes.length; i < n; ++i) {
        var node = nodes[i];
        node.y += (node.cy - node.y) * gravity;
        node.x += (node.cx - node.x) * gravity;
    }

    context.clearRect(0, 0, width, height);

    context.beginPath();
    for (var i = 0, n = links.length; i < n; ++i) {
        var link = links[i];
        context.moveTo(link.source.x, link.source.y);
        context.lineTo(link.target.x, link.target.y);
    }
    context.lineWidth = 1;
    context.strokeStyle = "#bbb";
    context.stroke();

    context.beginPath();
    for (var i = 0, n = nodes.length; i < n; ++i) {
        var node = nodes[i];
        context.moveTo(node.x, node.y);
        context.arc(node.x, node.y, 2, 0, τ);
    }
    context.lineWidth = 3;
    context.strokeStyle = "#fff";
    context.stroke();
    context.fillStyle = "#000";
    context.fill();
}

// Based on https://www.jasondavies.com/poisson-disc/
function poissonDiscSampler(width, height, radius) {
    var k = 30, // maximum number of samples before rejection
        radius2 = radius * radius,
        R = 3 * radius2,
        cellSize = radius * Math.SQRT1_2,
        gridWidth = Math.ceil(width / cellSize),
        gridHeight = Math.ceil(height / cellSize),
        grid = new Array(gridWidth * gridHeight),
        queue = [],
        queueSize = 0,
        sampleSize = 0;

    return function() {
        if (!sampleSize) return sample(Math.random() * width, Math.random() * height);

        // Pick a random existing sample and remove it from the queue.
        while (queueSize) {
            var i = Math.random() * queueSize | 0,
                s = queue[i];

            // Make a new candidate between [radius, 2 * radius] from the existing sample.
            for (var j = 0; j < k; ++j) {
                var a = 2 * Math.PI * Math.random(),
                    r = Math.sqrt(Math.random() * R + radius2),
                    x = s.x + r * Math.cos(a),
                    y = s.y + r * Math.sin(a);

                // Reject candidates that are outside the allowed extent,
                // or closer than 2 * radius to any existing sample.
                if (0 <= x && x < width && 0 <= y && y < height && far(x, y)) return sample(x, y);
            }

            queue[i] = queue[--queueSize];
            queue.length = queueSize;
        }
    };

    function far(x, y) {
        var i = x / cellSize | 0,
            j = y / cellSize | 0,
            i0 = Math.max(i - 2, 0),
            j0 = Math.max(j - 2, 0),
            i1 = Math.min(i + 3, gridWidth),
            j1 = Math.min(j + 3, gridHeight);

        for (j = j0; j < j1; ++j) {
            var o = j * gridWidth;
            for (i = i0; i < i1; ++i) {
                if (s = grid[o + i]) {
                    var s,
                        dx = s.x - x,
                        dy = s.y - y;
                    if (dx * dx + dy * dy < radius2) return false;
                }
            }
        }

        return true;
    }

    function sample(x, y) {
        var s = {
            x: x,
            y: y,
            cx: x,
            cy: y
        };
        queue.push(s);
        grid[gridWidth * (y / cellSize | 0) + (x / cellSize | 0)] = s;
        ++sampleSize;
        ++queueSize;
        return s;
    }
}
<script src="https://d3js.org/d3.v3.min.js"></script>
<section>

    <div id="canvas">

    </div>

</section>
1
Are you saying you don't want to use CSS or javascript to achieve this? What does that last sentence mean?HeadCode
@HeadCode No, If have solution with js or css are welcomeWesley Heron
The d3 library on its own will accomplish this for you no need to use canvasmarkE
@markE How it works? I don't know.Wesley Heron

1 Answers

2
votes

Canvas is like an image. It has it's own physical dimensions but can still be scaled with CSS. Use the width and height attributes to set the physical dimensions and then use CSS to scale it with a % to make it responsive.

Example...

var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
ctx.rect(20, 20, 150, 100);
ctx.stroke();
<canvas id="myCanvas" width="300" height="150" style="width:100%;"></canvas>