Canvas drawing experiment. How to add a loading graphic to canvas

Here is an experiment I’ve been working on. It draws shapes inspired by Islamic geometric principles.

http://www.webventions.com/applications/geometric_subdivision.html

  • I’m trying to figure out how to detect when the canvas is loaded so I can add a loading spinner.
  • Any thoughts on how to make this run faster would also be appreciated.
  • Any other thoughts…

Here are some parameters I like:
21/22/1
50/20/1
12/9/1
40/5/2

If you find any other cool ones let me know.

Thank you E

You can place the code within a jQuery callback to get started, but the spinner isn’t required when good coding is used.

With 25 I’ve turned this from taking 1.5 seconds to taking only 4 milliseconds, which is nearly well over 350 times faster.
The main thing that I did to improve on this is to remove the coords to string conversion, and remove as many global variables as possible.

I’ve also used objects to contain the x/y coords, which while they don’t provide much speed benefit, result in easier to read code.

Here’s the line_exists function from before:


function line_exists(coord1,coord2,subdivided_output) {
    
    coord1=coord1[0]+' '+coord1[1];
    coord2=coord2[0]+' '+coord2[1];
    
    new_cords=coord1+' '+coord2;
    new_cords_alt=coord2+' '+coord1;
    counter=1;
    for (iii = 0; iii < subdivided_output.length; iii++) {
    
        sub_coord1=subdivided_output[iii][0][0]+' '+subdivided_output[iii][0][1];
        sub_coord2=subdivided_output[iii][1][0]+' '+subdivided_output[iii][1][1];
        
        old_coord=sub_coord1+' '+sub_coord2;
        
        if(old_coord==new_cords || old_coord==new_cords_alt ) {
            //$('#data').append('<span style="color:red">'+old_coord+" :: "+new_cords+"</span><br/>");
            return true;
        }
        else {
            //$('#data').append(counter+' ::: '+old_coord+" :: "+new_cords+'<br />' );
            counter++;
        }
        
        //$('#data').append(old_coord+" :: "+new_cords+'<br />' );
    }
    
    return false;
    
}

and the line_exists function after the changes


function line_exists(lineFrom, lineTo, subdivided_output) {
    var i,
        from,
        to,
        sameStart,
        sameEnd,
        switchedStart,
        switchedEnd;
        
    for (i = 0; i < subdivided_output.length; i += 1) {
        from = subdivided_output[i].from;
        to = subdivided_output[i].to;

        sameStart = (from.x === lineFrom.x && from.y === lineFrom.y);
        sameEnd = (to.x === lineTo.x && to.y === lineTo.y);
        switchedStart = (from.x === lineTo.x && from.y === lineTo.y);
        switchedEnd = (to.x === lineFrom.x && to.y === lineFrom.y);
        
        if (sameStart && sameEnd || switchedStart && switchedEnd) {
            return true;
        }
    }
    return false;
}

Another example of improvement in terms of performance is in the subdivide function. Before it was looping through all lines and then using modulus with skip:


for (ii = 0; ii < output.length; ii++) {
    if([b]ii%skip==0[/b] && output[i] != output[ii] && line_exists(output[i],output[ii], subdivided_output)==false){
        ...

So with a skip of 4, that is similar to this:
0. ii % skip = 0 so use this

  1. ii % skip = 1 so don’t use this
  2. ii % skip = 2 so don’t use this
  3. ii % skip = 3 so don’t use this
  4. ii % skip = 0 so use this
  5. ii % skip = 1 so don’t use this
  6. ii % skip = 2 so don’t use this
  7. ii % skip = 3 so don’t use this
  8. ii % skip = 0 so use this

The updated code doesn’t need that check, because it just loops by the skip amount:


for (to = 0; to < output.length; to += skip) {
    if (output[from] !== output[to] && !line_exists(output[from], output[to], subdivided_output)) {
        ...

So with a skip of 4, that is similar to this:
0. use this
4. use this
8. use this

The improved speed also means that most of your size constraints shouldn’t be required now.

The working code is up at http://jsfiddle.net/pmw57/kJVjR/2/
Put in sides of 25, or 40, and you’ll find that there is now an instantaneous response time.

Very Awesome! You’ve given me a lot to chew on. I’ll go through and review it until I understand all of it. I’ve been wanting to add sliders it sounds like with this information it will be practical.

E

The steps that I took to achieve that are:

  1. www.jsbeautifier.com to provide a consistent presentation
  2. www.jslint.com to find (and fix) as many obvious problems as possible
  3. remove that string conversion for the coords
  4. change some variables and/or data schemes to make the code easier to understand
  5. rework the step situation

Thanks for these tips. I don’t understand some of this syntax. Could you explain a few things?

In the section below, what does the ‘.from’ do?

  from = subdivided_output[i].from;

In this section, how is “===” diferent from “==” and how are x and y defined?

        sameStart = (from.x === lineFrom.x && from.y === lineFrom.y);

Many thanks

To clarify, the “from =” is a completely different “from” than the one used in “.from” at the end.
One is a named variable, while the other is an object property.

Previously, push was being used to add items to the subdivided_output array:

subdivided_output.push(Array(output[i],output[ii]);

Using the push method with only one item tends to be slower than other techniques, so to squeeze more performance out of this section, we can use the array length to add the item to the end of that array.
It’s also preferable to use [arrayItem] instead of new Array(arrayItem) too.
I also renamed the for loops to make it clear what they are referring to.

subdivided_output[subdivided_output.length] = [output[from],output[to]];

Later on, we had lots of different array index being used:

sub_coord1=subdivided_output[iii][0][0] + ...

So make things nice and clear, we can store the array items as named objects instead.


subdivided_output[subdivided_output.length] = {'from': output[from], 'to': output[to]};

Those output items are also objects, which we’ll get in to below, so code to access the subdivided_output values can now be:

sub_coord1=subdivided_output[iii]['from']['x'] + ...

Which can also be done without the array notation, as:

sub_coord1=subdivided_output[iii].from.x + ...

Using [‘from’] or .from, those are not array variables. They are just ways to access object properties.

That line of code might be easier to understand if the variables are adjusted slightly, so that instead of using from/to to store the coord, we use start/end instead.
That way we would have code like this:


start = subdivided_output[i].from;

The triple equals is used to ensure that both side really are equal.
The double equal allows far too many strange cases to slip through, and programmers are almost certainly not aware of all variations, so it’s best advised to stay away from using the double equals.
Some of this is explained in articles such as The pleasures and perils of JavaScript’s promiscuous comparison operator or in slides like [url=“http://www.powershow.com/view/a6800-ZWEzM/JavaScript_The_Good_Parts_Part_Two_A_Survey_of_the_Language_flash_ppt_presentation”]The Good Parts Part Two (slides 31 & 32) which you can also see explained in good detail in [url=“http://www.youtube.com/watch?v=hQVTIJBZook&feature=player_detailpage#t=899s”]JavaScript: The Good Parts [from 14:59] (video)

x and y are defined in the shape function. They used to be array items, such as:

points=new Array(x,y);
output.push(points);

which is the same as doing:

output[i] = [x, y];

however, storing them as array items results in needing to use [0] and [1] to access their values, and they are actually different types of values, so it makes better sense and is easier to read when we store and access them as x and y properties instead.

output[i] = {
    x: Math.round(size * (1 + Math.cos(angle))),
    y: Math.round(size * (1 + Math.sin(angle)))
};

It also means that what used to be this:


function shape(sides, size){
    output=new Array;
    x=0;
    y=0;
    for (i = 0; i < (sides+1); i++) {
        x= size * Math.cos(2 * Math.PI * i / sides);
        y=size * Math.sin(2 * Math.PI * i / sides);
        x= x+size;
        y= y+size;
        x=Math.round(x);
        y=Math.round(y);
        points=new Array(x,y);
        output.push(points);
    }
    return output;
}

is now easier to understand as:


function shape(sides, size) {
    Math.TAU = Math.PI * 2;

    var output = [],
        i,
        slices = Math.TAU / sides,
        angle;
    for (i = 0; i <= sides; i += 1) {
        angle = slices * i;
        output[i] = {
            x: Math.round(size * (1 + Math.cos(angle))),
            y: Math.round(size * (1 + Math.sin(angle)))
        };
    }
    return output;
}

Paul,
Thanks for all this information.
You’ve given me a lot to work with.
I really appreciate this.
E