Как сделать адаптивную анимацию холста, используя библиотеку D3

Я занимаюсь разработкой простой анимации с использованием библиотеки d3.js и показываю ее через canvas, но мой canvas не адаптивен.

В начале кода я установил исходную ширину 960 и высоту 500, но как я могу сделать его изменяемым (отзывчивым)? Я не использую CSS, чтобы применить некоторый стиль (ширину и высоту) к холсту, я использую JavaScript, чтобы сделать это. Решение может быть CSS или 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)

Ваш ответ на вопрос