Pretty Circles

The functions in this class require Underscore's library, and it should be loaded on the HTML page prior to including this module.

We can't pass an operator into Underscore's reduce function, so we simply create an add function:

add = (x, y) -> x + y

The sum function use's Underscore's reduce function calling the add function on each element in our lst.

sum = (lst) -> _.reduce(lst, add, 0)

A roll function takes how_many dice to roll, and also the number of sides those dice have. Basically, the range for the random numbers.

roll = (how_many, sides) ->
    dice = -> _.random(1,sides)
    sum( _.times(how_many, dice) )

We now use the roll function to skew the results towards a particular goal within a range:

goal_roll = (goal, range) ->
    steep = 20             # Number of dice to roll
    sides = (range + steep) / steep  # Number of sides on the dice
    delta = goal - range / 2

    results = roll(steep, sides) - steep + delta
    console.log "Roll:", results, sides, goal, delta
    if (results > range)
        return results - range
    else
        return results

We now pick a random color (hue, actually) that clusters around a particular goal between 1 and 100:

get_color = (goal) ->
    hue = goal_roll(goal, 100) / 100
    sat = _.random(.8, .9)
    lit = _.random(.5, .6)
    hslToRgb(hue, sat, lit)

Converts an HSL color value to RGB. Conversion formula adapted from http://en.wikipedia.org/wiki/HSL_color_space. Assumes h, s, and l are contained in the set [0, 1] and returns r, g, and b in the set [0, 255].

hslToRgb = (h, s, l) ->
        if (s == 0)
                r = g = b = l   # achromatic
        else
                hue2rgb = (p, q, t) ->
                    if (t < 0) then t += 1
                    if (t > 1) then t -= 1

                    if (t < 1/6)
                        p + (q - p) * 6 * t
                    else if (t < 1/2)
                        q
                    else if (t < 2/3)
                        p + (q - p) * (2/3 - t) * 6
                    else
                        p

                q = if l < 0.5 then l * (1 + s) else l + s - l * s
                p = 2 * l - q

                r = hue2rgb(p, q, h + 1/3);
                g = hue2rgb(p, q, h);
                b = hue2rgb(p, q, h - 1/3);

        # return [r * 255, g * 255, b * 255];
        "#" + dec2hex(r*255) + dec2hex(g*255) + dec2hex(b*255);

Since our RGB color values need to be 0 padded when they are converted to hex, we have this lovely little function:

dec2hex = (v) ->
    if (v < 16)
        "0" + Math.floor(v).toString(16)
    else
        Math.floor(v).toString(16);

While the HTML5 canvas is nice, it good use a little helper function to draw a circle with a single command:

disk = (canvas, color, x, y, radius) ->
    canvas.fillStyle = color
    canvas.beginPath()
    canvas.arc(x, y, radius, 0, Math.PI*2, true)
    canvas.closePath()
    canvas.fill()

Now it is time to acquire a drawing canvas and go to work, but not until the HTML has been loaded with the window.onload event:

window.onload = ->
        drawingCanvas = document.getElementById('pretty-circles')

        # Check the element is in the DOM and the browser supports canvas
        if drawingCanvas.getContext

                # Initaliaze a 2-dimensional drawing context
                context = drawingCanvas.getContext('2d')
                hue = _.random(1,100)

                for x in [10..490] by 20
                        for y in [10..490] by 20
                            disk  context, get_color(hue), x, y, _.random(2,10)

Author: Howard Abrams

Emacs 24.3.1 (Org mode 8.2.4)

Validate