Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Create external JavaScript file and call it on HTML5 canvas

I'm using D3.js library to create example animation in the simple HTML5 canvas.

But I won't use <script> inside HTML code. I need to create a file and put it there. So, how may I call it in HTML code?

This is my html file:

<!DOCTYPE html>
<html>
    
<head>
    <meta charset="utf-8">
    <link rel="stylesheet" type="text/css" href="css/estilo.css">
    <script src="https://d3js.org/d3.v3.min.js"></script>
    <script src="js/delaunayanimation.js"></script>
    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
</head>
<body>
    <section>
        <canvas id="canvas">
            //how to call javascript here?
        </canvas>
    </section>
</body>
</html>

And this is my script that I created as external (its name is delaunayanimation.js):

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;
  }
}
like image 918
Wesley Heron Avatar asked Jun 22 '26 02:06

Wesley Heron


2 Answers

You need to wait for the items such as the canvas to load in the DOM before running your code. You can either keep it in the <head> tag and wrap your code in document.ready or you can move it to the bottom of the body like so:

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <link rel="stylesheet" type="text/css" href="css/estilo.css">
    <script src="https://d3js.org/d3.v3.min.js"></script>
    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
</head>
<body>
    <section>
        <canvas id="canvas">
            //how to call javascript here?
        </canvas>
    </section>
    <script src="js/delaunayanimation.js"></script>
</body>
</html>
like image 116
Spencer Wieczorek Avatar answered Jun 24 '26 16:06

Spencer Wieczorek


Your problem is that you're trying to append a <canvas> inside a <canvas>, which is pointless. This line:

var canvas = d3.select("#canvas").append("canvas")

looks for an element with ID "canvas" and append a <canvas> inside it. But in your HTML, that element is a canvas:

<section>
    <canvas id="canvas">
    </canvas>
</section>

If you just change this to a <div>, for instance:

<section>
    <div id="canvas">
    </div>
</section>

your code will work. Here is a working fiddle to show you (the only thing I did was the above mentioned change): https://jsfiddle.net/vcg1pk9v/1/

Also, have Spencer's answer in mind: the code that manipulates that div has to come after that div is created (in JSFiddle, the code will run after automatically). A good practice is putting it (or calling it, if external) just before closing the <body>. That being said, your question has actually two problems; Spencer dealt with one of them, I dealt with the other.

like image 20
Gerardo Furtado Avatar answered Jun 24 '26 15:06

Gerardo Furtado



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!