Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Olympics rings with JavaScript on HTML5 canvas

<center><canvas id=c1 width=400 height=400></canvas>
<script>
    ctx = c1.getContext('2d')
    ctx.fillStyle = '#7ef' // draw blue background with the darker frame
    ctx.fillRect(0, 0, 400, 400)
    ctx.fillStyle = '#9ff'
    ctx.fillRect(10, 10, 400-20, 400-20)

    var X = 75, W = 50, G = 20
    ctx.lineWidth = 10
    var colors = ['blue', 'black', 'red', 'yellow', 'green']
    var args = [
        [X,X,W],
        [X+W+W+G,X,W],
        [X+W+W+G+W+W+G,X,W],
        [X+W+G/2,X+W,W],
        [X+W+G/2+W+W+G,X+W,W]]

    while (colors.length > 0) {
        ctx.strokeStyle = colors.shift()
        ctx.beginPath()
        ctx.arc.apply(ctx, args.shift().concat([0,Math.PI*2,true]))
        ctx.stroke()
    }
</script>

Above is my code at this moment. My goal is to amuse children, there are 12 year old boys, but my code is not amazing enough, is it possible to make it less boring by removing all hand-coded coordinates? Also it would be cool to make rings "interconnected", but how to achieve that?

Here is the output of my current code:

enter image description here

And this is what Olympics rings are supposed to look like:

enter image description here

like image 862
exebook Avatar asked Jan 17 '14 07:01

exebook


2 Answers

For the 12 year olds!

I wrote some code for you which is not necessarily boring or amusing, easy or difficult, but it gets the job done:

var canvas = document.getElementById('c1').getContext('2d');
var radius = 50;

var circles = [
  {
    color:'blue',
    x : 2*radius - radius/2,
    y : 2*radius,
    isTop: true
  } , {
    color:'black',
    x : 4*radius,
    y : 2*radius,
    isTop: true
  } , {
    color:'red',
    x : 6*radius + radius/2,
    y : 2*radius,
    isTop: true
  } , {
    color:'yellow',
    x : 3*radius - radius/4,
    y : 3*radius,
    isTop: false
  } , {
    color:'green',
    x : 5*radius + radius/4,
    y : 3*radius,
    isTop: false
  }
];

function drawArc(canvas, color, x, y, start, end) {
  if (color !== 'white') drawArc(canvas, 'white', x, y, start, end);

  canvas.lineWidth = color === 'white' ? 16 : 10;
  canvas.strokeStyle = color;

  canvas.beginPath();
  canvas.arc(x, y, radius, start - Math.PI/2, end - Math.PI/2, true);
  canvas.stroke();
}

circles.forEach(function(circle){
  drawArc(canvas, circle.color, circle.x, circle.y, 0, Math.PI*2);
});

circles.forEach(function(circle){
  if (circle.isTop) {
     drawArc(canvas, circle.color, circle.x, circle.y, Math.PI, Math.PI*2/3);
     drawArc(canvas, circle.color, circle.x, circle.y, Math.PI*5/3, Math.PI*4/3);
  } else {
     drawArc(canvas, circle.color, circle.x, circle.y, 0, Math.PI/3);
     drawArc(canvas, circle.color, circle.x, circle.y, Math.PI*2/3, Math.PI/3);
  }
});

http://jsbin.com/IrOJOhIg/1/edit

If i were to explain the code, i'd start with the circles variable, which is an array which says for each circle it's color, center and whether it is on the top row or not. I would comment out the += radius/2 and radius/4 parts and run the code for them, showing that the circles are too tight together, and uncomment them to show that changing the x coordinate moves them apart.

I would then explain the drawArc function which draws a part of a circle, first with white and then with the actual color, in different line widths. This is pretty much the most difficult part of the whole script.

Finally, i would run the script again with the final forEach commented out, to show that the last rings drawn completely cover the previous ones and ask the 12 year olds for a solution. The solution you should aim for is drawing the circles in parts.

I have split the circle in 6 pieces starting from the top and if you take a good glance at them you will see that the same part can be either covered or on top if the circle is on the top row or not. The final for each redraws the 2 parts of each circle that must be on top in intersections.

Finally, bonus points for the 12 year old who notices that the intersections are actually reversed in my code and more bonus points for the one who comes up with a solution. (Obviously the simplest solution is to fiddle with the last forEach). EDIT: Actually just putting the condition as !circle.isTop is ever simpler.

PS: There are some rounding errors that result in thin white lines where the arcs meet. They can be fixed, but i didn't bother with them.

like image 160
Tibos Avatar answered Oct 11 '22 17:10

Tibos


JSFIDDLE

var canvas = document.getElementById('c1').getContext('2d');
var radius = 50;

var circles = [
    { color:'blue',   x: 2*radius - radius/2, y: 2*radius, q: [1,2,3,0] },
    { color:'black',  x: 4*radius,            y: 2*radius, q: [2,0,1,3] },
    { color:'red',    x: 6*radius + radius/2, y: 2*radius, q: [2,0,1,3] },
    { color:'yellow', x: 3*radius - radius/4, y: 3*radius, q: [3,0,1,2] },
    { color:'green',  x: 5*radius + radius/4, y: 3*radius, q: [3,0,1,2] }
];

function drawArc(canvas, circle, q) {
    var s = (circle.q[q]+0.5)/2 * Math.PI,
        e = (circle.q[q]-0.5)/2 * Math.PI;

    canvas.lineWidth   = 16;
    canvas.strokeStyle = 'white';
    canvas.beginPath();
    canvas.arc( circle.x, circle.y, radius, s, e, true );
    canvas.stroke();

    canvas.lineWidth   = 10;
    canvas.strokeStyle = circle.color;
    canvas.beginPath();
    canvas.arc( circle.x, circle.y, radius, s, e, true );
    canvas.stroke();
}

for ( var q = 0; q < 4; ++q ){
    circles.forEach(function(circle){
        drawArc( canvas, circle, q );
    })
}

At each step of the for loop a quarter arc (quadrant) is drawn for each circle - circle.q determines the order in which the quadrants are drawn around the circle and by careful ordering you can get it to draw the quadrants going under another ring before that other ring is drawn on top.

(Thanks to Tibos for the initial code)

If you want to make it more "interesting" then you can make the arc lengths smaller and add a delay between each draw to "animate" the appearance of the rings.

like image 33
MT0 Avatar answered Oct 11 '22 16:10

MT0