3
\$\begingroup\$

I'm creating a web app to create pixel maps for large LED displays. The maps are basically large checkerboard patterns of various sizes with different constraints.

I'm running into snags trying to draw arrows pointing in certain directions dependent on their position within a) the entire grid, and b) within smaller sub-sections of the map.

Upon loading the jsfiddle, you can scroll down and see a correct example. The arrows snake through the red/blue area of the map from left to right, going down one row at a time.

Now if you select the second data flow option (the 2nd radio button from the left on the top row of radio buttons) you'll see the direction of the arrows change but the map doesn't draw correctly.

I need help getting this running efficiently. Nested loops within loops within loops seems slow. Plus I'm just in over my head and a bit confused. There's a working earlier version of the app at (http://www.blinkingthings.com) if you need a more complete picture of what I'm asking for.

I'd also appreciate any criticism of this javascript as it's not my expertise.

Javascript:

$(function(){
    var canvas=document.getElementById("canvas");
    var ctx=canvas.getContext("2d");

    var width=128;
    var height=128;
    var columns=16;
    var rows=9;

    var color1="#d9534f";//redish
    var color2="#428bca";//bluish
    var color4="#00FF00";//greenish
    var color3="#FFFF00";//yellowish

    var textcolor="#FFFFFF";
    var datacolor="#FFFFFF";
    var bordercolor ="#5cb85c";
    var dataStartColor ="#5cb85c";
    var infoBackgroundColor = "rgba(255,255,255,.01)";
    var infoForegroundColor = "rgba(0,0,255,.1)";

    var upArr = '\u2191';
    var downArr = '\u2193';
    var leftArr = '\u2190';
    var rightArr = '\u2192';
    var stopSign = '\uD83D\uDEAB';
    var omega = '\u03A9';

    var oddOrEven = "odd";
    var colOddEven = "odd";

    var dataFlow = "1";
    var drawCoords = true;
    var drawData = true;
    var drawInfo = true;
    var drawUser = false;

    var counter = 1;

    var xStart=0; 
    var yStart=0;

    var resWidthLimit=1920;
    var resHeightLimit=1080;
    var colsLimit = Math.floor(resWidthLimit/width);
    var rowsLimit = Math.floor(resHeightLimit/height);

    var outputsHigh = Math.ceil(rows/rowsLimit);
    var outputsWide = Math.ceil(columns/colsLimit);

    var alphabet = "a b c d e f g h i j k l m n o p q r s t u v w x y z aa bb cc dd ee ff gg hh ii jj kk ll mm nn oo pp qq rr ss tt uu vv ww xx yy zz aaa bbb ccc ddd eee fff ggg hhh iii jjj kkk lll mmm nnn ooo ppp qqq rrr sss ttt uuu vvv www xxx yyy zzz".split(" ");

    var topEdge = false;
    var bottomEdge = false;
    var leftEdge = false;
    var rightEdge = false;
    var rowsOdd = false;
    var columnsOdd = false;

    var outTopEdge = false;
    var outBottomEdge = false;
    var outLeftEdge = false;
    var outRightEdge = false;
    var outRowsOdd = false;
    var outColumnsOdd = false;


    // references to the input-text elements 
    // used to let user change the rect width & height
    var $width=document.getElementById('width');
    var $height=document.getElementById('height');
    var $rows=document.getElementById('rows');
    var $columns=document.getElementById('columns');
    var $resWidthLimit=document.getElementById('resWidthLimit');
    var $resHeightLimit=document.getElementById('resHeightLimit');
    var $tileSwap=document.getElementById('tileSwap');
    var $wallSwap=document.getElementById('wallSwap');
    var $outputSwap=document.getElementById('outputSwap');
    var $tilePresets=document.getElementById('tilePresets');
    var $outputPresets=document.getElementById('outputPresets');
    var $colSlide=document.getElementById('colSlide');
    var $rowSlide=document.getElementById('rowSlide');
    var $radio1=document.getElementById('radio1');
    var $radio2=document.getElementById('radio2');
    var $radio3=document.getElementById('radio3');
    var $radio4=document.getElementById('radio4');
    var $radio5=document.getElementById('radio5');
    var $radio6=document.getElementById('radio6');
    var $radio7=document.getElementById('radio7');
    var $radio8=document.getElementById('radio8');  
    var $drawcoords=document.getElementById('draw_coords_check');
    var $drawdata=document.getElementById('draw_data_check');
    var $drawinfo=document.getElementById('draw_info_check');
    var $drawuser=document.getElementById('draw_user_check');

    // set the initial input-text values to the width/height vars
    $width.value=width;
    $height.value=height;
    $rows.value=rows;
    $columns.value=columns;
    $resWidthLimit.value=resWidthLimit;
    $resHeightLimit.value=resHeightLimit;

    $width.addEventListener("change", function(){
        width=this.value; //.value converts input field int to string***
        outputsWide = Math.ceil(columns/colsLimit);
        colsLimit = Math.floor(resWidthLimit/width);
        draw();
    }, false);

    $height.addEventListener("change", function(){
        height=this.value; //.value converts input field int to string***
        outputsHigh = Math.ceil(rows/rowsLimit);
        rowsLimit = Math.floor(resHeightLimit/height);
        draw();
    }, false);

    $rows.addEventListener("keyup", function(){
        rows=this.value; //.value converts input field int to string***
        outputsHigh = Math.ceil(rows/rowsLimit);

        draw();
    }, false);

    $columns.addEventListener("keyup", function(){
        columns=this.value; //.value converts input field int to string***
        outputsWide = Math.ceil(columns/colsLimit);
        draw();
    }, false);

    $resWidthLimit.addEventListener("keyup", function(){
        resWidthLimit=this.value; //.value converts input field int to string***
        colsLimit = Math.floor(resWidthLimit/width);
        outputsWide = Math.ceil(columns/colsLimit);
        setTimeout(function() { draw(); }, 500); //had to add small delay to prevent crashing. look into
    }, false);

    $resHeightLimit.addEventListener("keyup", function(){
        resHeightLimit=this.value; //.value converts input field int to string***
        rowsLimit = Math.floor(resHeightLimit/height);
        outputsHigh = Math.ceil(rows/rowsLimit);
        setTimeout(function() { draw(); }, 500); //had to add small delay to prevent crashing. look into
    }, false);

    $wallSwap.addEventListener("click", function(){
        var temp = $('#columns').val();
        $('#columns').val($('#rows').val());
        $('#rows').val(temp);
        columns=$('#columns').val();
        rows=$('#rows').val();
        outputsWide = Math.ceil(columns/colsLimit);
        outputsHigh = Math.ceil(rows/rowsLimit);
        draw();
    }, false);

    $tileSwap.addEventListener("click", function(){
        var temp = $('#width').val();
        $('#width').val($('#height').val());
        $('#height').val(temp);
        width=$('#width').val();
        height=$('#height').val();
        outputsWide = Math.ceil(columns/colsLimit);
        colsLimit = Math.floor(resWidthLimit/width);
        outputsHigh = Math.ceil(rows/rowsLimit);
        rowsLimit = Math.floor(resHeightLimit/height);
        draw();
    }, false);

    $outputSwap.addEventListener("click", function(){
        var temp = $('#resWidthLimit').val();
        $('#resWidthLimit').val($('#resHeightLimit').val());
        $('#resHeightLimit').val(temp);
        resWidthLimit=$('#resWidthLimit').val();
        resHeightLimit=$('#resHeightLimit').val();
        colsLimit = Math.floor(resWidthLimit/width);
        rowsLimit = Math.floor(resHeightLimit/height);
        draw();
    }, false);

    $tilePresets.addEventListener("click", function(){
        width=$('#width').val();
        height=$('#height').val();
        outputsHigh = Math.ceil(rows/rowsLimit);
        rowsLimit = Math.floor(resHeightLimit/height);
        outputsWide = Math.ceil(columns/colsLimit);
        colsLimit = Math.floor(resWidthLimit/width);
        draw();
    }, false);

    $outputPresets.addEventListener("click", function(){
        resWidthLimit=$('#resWidthLimit').val();
        resHeightLimit=$('#resHeightLimit').val();
        rowsLimit = Math.floor(resHeightLimit/height);
        colsLimit = Math.floor(resWidthLimit/width);
        draw();
    }, false);

    $radio1.addEventListener("click", function(){
        dataFlow=this.value; //.value converts input field int to string***
        draw();
    }, false);

     $radio2.addEventListener("click", function(){
        dataFlow=this.value; //.value converts input field int to string***
         draw();
    }, false);

     $radio3.addEventListener("click", function(){
        dataFlow=this.value; //.value converts input field int to string***
         draw();
    }, false);

     $radio4.addEventListener("click", function(){
        dataFlow=this.value; //.value converts input field int to string***
         draw();
    }, false);  

     $radio5.addEventListener("click", function(){
        dataFlow=this.value; //.value converts input field int to string***
         draw();
    }, false);

     $radio6.addEventListener("click", function(){
        dataFlow=this.value; //.value converts input field int to string***
         draw();
    }, false);

     $radio7.addEventListener("click", function(){
        dataFlow=this.value; //.value converts input field int to string***
        draw();
    }, false);

     $radio8.addEventListener("click", function(){
        dataFlow=this.value; //.value converts input field int to string***
        draw();
    }, false); 

     $drawcoords.addEventListener("change", function(){
        drawCoords = !drawCoords; //flips boolean of drawCoords t/f
        draw();
    }, false); 

     $drawdata.addEventListener("change", function(){
        drawData = !drawData; //flips boolean of drawData t/f
        draw();
    }, false);

    $drawinfo.addEventListener("change", function(){
        drawInfo = !drawInfo; //flips boolean of drawInfo t/f
        draw();
    }, false);

    $drawuser.addEventListener("change", function(){
        drawUser = !drawUser; //flips boolean of drawUser t/f
        draw();
    }, false);

    draw();//inital draw

    function draw(){
        clearAll();

        tilesByOutput();

    }//end draw()

    function clearAll(){
        ctx.canvas.width = +width * +columns; //set entire canvas width
        ctx.canvas.height = +height * +rows; //set entire canvas height
        ctx.clearRect(0,0,canvas.width,canvas.height); //clear out entirety of canvas

        xStart=0; //reset x coord to 0 (default)
        yStart=0; //reset y coord to 0 (default)
    }

    function allTiles(){
        for (var j = 0; j < rows; j++){ //for every row under limit
            for (var i = 0; i < columns; i++) { //for every column under limit
                (i % 2) !=1 ? colOddEven = "odd" : colOddEven = "even"; //every other column
                if ((j % 2) !=1){ //for every other row
                    oddOrEven = "odd";
                    (i % 2) != 1 ? ctx.fillStyle=red : ctx.fillStyle=blue;//start alternating patter with color option 1
                 } else {
                    oddOrEven = "even";
                    //start alternating patter with color option 2
                    (i % 2) != 1 ? ctx.fillStyle=blue : ctx.fillStyle=red;
                 } //end if/else
                ctx.fillRect(xStart,yStart,width,height); //draw single tile
                console.log("Single Tile Drawn at : (" + xStart + ", " + yStart + ")");
                xStart = +xStart + +width; //shift starting coords for next column
            }//end columns for
            xStart = 0;  //reset x coord to 0 (default) for begining of next row
            yStart = +yStart + +height;  //shift starting coords for next row
        }//end rows for
    }//end allTiles

    function tilesByOutput(){

        for (var l=0; l<=outputsWide; l++){//for each necessary output (width)
            //console.log("Output Width = " + l);
            for(var k=0; k<=outputsHigh; k++){//for each necessary output (height)

                xStart = +colsLimit*l * +width;//0 on first loop, moves to right edge of output after
                yStart = +rowsLimit*k * +height;//0 on first loop, moves to bottom edge of output after

                for (var j = rowsLimit*k; (j < rowsLimit*(k+1)); j++){ //for every row wihtin current output's limit
                    for (var i = colsLimit*l; (i < colsLimit*(l+1) ); i++) { //for every column within current output's limit

                        if (i>columns-1) //previous loops are running too many times, this safeguards them.
                        {
                            continue;
                        } else if (j>rows-1){
                            continue;
                        }//end of for loop safeguard

                        var xLimit = i-(colsLimit*l);
                        var yLimit = j-(rowsLimit*k);

                        i == columns-1 ? rightEdge = true : rightEdge = false;          
                        i == 0 ? leftEdge = true : leftEdge = false;
                        j == rows-1 ? bottomEdge = true : bottomEdge = false;
                        j == 0 ? topEdge = true : topEdge = false;
                        (i % 2) != 1 ? columnsOdd = true : columnsOdd = false;      
                        (j % 2) != 1 ?  rowsOdd = true : rowsOdd = false;

                        xLimit == colsLimit-1 ? outRightEdge = true : outRightEdge = false;         
                        xLimit == 0 ? outLeftEdge = true : outLeftEdge = false;
                        yLimit == rowsLimit-1 ? outBottomEdge = true : outBottomEdge = false;
                        yLimit == 0 ? outTopEdge = true : outTopEdge = false;
                        (xLimit % 2) != 1 ? outColumnsOdd = true : outColumnsOdd = false;       
                        (yLimit % 2) != 1 ? outRowsOdd = true : outRowsOdd = false;

                        (i % 2) !=1 ? colOddEven = "odd" : colOddEven = "even"; //every other column odd or even (for data arrows)
                        //Step 1 : Figure out background color for current tile.
                        (l % 2) !=1 ? ( //if output x coord (l) is odd combo 1 : even combo 2
                            (k % 2) !=1 ? ( //if output y coord is odd (k) combo 1 : even combo 2
                                (outRowsOdd) ? ( //for every other row                           //rows: every other row in current output alternate between c1/c2
                                    oddOrEven = "odd",
                                    (outColumnsOdd) ? ctx.fillStyle=color1 : ctx.fillStyle=color2//columns: odds c1 : evens c2 (first row, first column is color1)
                                ) : ( //middle j                                                next row
                                    oddOrEven = "even",
                                    (outColumnsOdd) ? ctx.fillStyle=color2 : ctx.fillStyle=color1//columns: odds c2 : evens c1
                                ) //end j                                                       end of rows for output v1
                            ) : ( //middle k end of combo 1, begin combo 2                      //output columns: 
                                (outRowsOdd) ? ( //for every other row                           //rows: every other row in current output alternate between c3/c4
                                    oddOrEven = "odd",
                                    (outColumnsOdd) ? ctx.fillStyle= color3: ctx.fillStyle=color4//columns: odds c3 : evens c4
                                ) : (//middle j                                               //rows:   next row
                                    oddOrEven = "even",
                                    (outColumnsOdd) ? ctx.fillStyle=color4 : ctx.fillStyle=color3//columns: odds c4 : evens c3 
                                ) //end j                                                   //rows:
                            )//end k                                                        //output columns: 
                        ) : ( //middle l                                                    //output rows:
                        //end output color alternation 1
                            (k % 2) !=1 ? ( //if output y coord is odd (k) alternate output colorcombos (red/blue or green/yellow)
                                (outRowsOdd) ? ( //for every other row
                                    oddOrEven = "odd",
                                    (outColumnsOdd) ? ctx.fillStyle=color3 : ctx.fillStyle=color4//start alternating patter with color option 1
                                ) : ( //middle j
                                    oddOrEven = "even",
                                    //start alternating patter with color option 2
                                    (outColumnsOdd) ? ctx.fillStyle=color4 : ctx.fillStyle=color3
                                ) //end j
                            ) : ( //middle k
                                (outRowsOdd) !=1 ? ( //for every other row
                                    oddOrEven = "odd",
                                    (outColumnsOdd) ? ctx.fillStyle= color1: ctx.fillStyle=color2//start alternating patter with color option 1
                                ) : ( //middle j
                                    oddOrEven = "even",
                                    //start alternating patter with color option 2
                                    (outColumnsOdd) ? ctx.fillStyle=color2 : ctx.fillStyle=color1
                                ) //end j
                            )//end k
                        )//end l
                        ctx.fillRect(xStart,yStart,width,height); //draw single tile

                        counter++;
                                                //alert(yLimit + " ," + xLimit);

                        //draw alphanumeric coordinates
                        if (drawCoords){ //check drawCoords checkbox. no check = no coords
                            var alphaX = xStart;
                            var alphaY = yStart;
                            ctx.fillStyle=textcolor;
                            if (+width < 35 || +height < 35){ //tile size check
                                ctx.font = "8px Helvetica";
                                alphaX = xStart+4;
                                alphaY = yStart+15;
                            } else if (+width < 54 || +height < 54){ //tile size check
                                ctx.font = "10px Helvetica";
                                alphaX = xStart+6;
                                alphaY = yStart+15;
                            } else if (+width < 64 || +height < 64){ //tile size check
                                ctx.font = "14px Helvetica";
                                alphaX = xStart+6;
                                alphaY = yStart+20;
                            } else if (+width < 110 || +height < 110){ //tile size check
                                ctx.font = "24px Helvetica";
                                alphaX = xStart+6;
                                alphaY = yStart+30;
                            } else {
                                ctx.font = "30px Helvetica";
                                alphaX = xStart+10;
                                alphaY = yStart+40;
                            }//end size check
                            ctx.fillText(alphabet[xLimit].toUpperCase() + (yLimit+1),alphaX,alphaY); //draw alpha then number
                        } 

                        //ARROW DIRECTION DETERMINATION
                        //draw data flow (default for starting top left with horizontal rows. 
                        var arrow = downArr; //default direction
                        switch (dataFlow){
                            case "1"://dataFlow=1
                                //(j % 2) != 1 ? arrow = rightArr : arrow = leftArr; //even/odd row check
                                i == columns-1 ? ( //last column of entire map
                                    (outRowsOdd) ? arrow = downArr : //output row odd 
                                        (columns == (colsLimit*l)+1 && i == columns-1) ? //only 1 column of rightmost output? 
                                        arrow = downArr : arrow = leftArr//far right column + single : far right column part of bigger output
                                ) : ( //everything but last column of entire map
                                    (outRowsOdd) ? 
                                        (arrow = rightArr, xLimit == colsLimit-1 ? arrow = downArr : arrow = rightArr )//odd rows default to right, last column of output down, otherwise right.
                                            : xLimit == 0 ? arrow = downArr /*even+left edge*/
                                                : arrow = leftArr/*even*/
                                );//far right edge check
                                break;

                            case "2"://dataFlow=2

                                i == columns-1 ? ( //last column of entire map
                                    (outRowsOdd) ? arrow = downArr : //output row odd 
                                        (columns == (colsLimit*l)+1 && i == columns-1) ? //only 1 column of rightmost output? 
                                        arrow = downArr : arrow = downArr//far right column  + single : far right column part of bigger output
                                ) : ( //everything but last column of entire map
                                    (outRowsOdd) ? 
                                        (arrow = leftArr, xLimit == colsLimit-1 ? arrow = leftArr : arrow = leftArr )//odd rows default to right, last column of output down, otherwise right.
                                            : xLimit == 0 ? arrow = downArr /*even+left edge*/
                                                : arrow = rightArr/*even*/
                                );//far right edge check            

                                break;

                        }

                        //ARROW COLOR DETERMINATION
                        if (drawData){ //check drawData checkbox. no check = no data path
                            ctx.fillStyle=datacolor;
                            if (dataFlow == 1){ //check which data case and tile being drawn to change first tile in data chain's arrow to green
                                if (yLimit== 0 && xLimit== 0){ //if top left
                                    ctx.fillStyle = dataStartColor;
                                } 
                                if (rowsOdd) {//if odd row
                                    if (yLimit==rowsLimit-1 || j==rows-1){//if odd bottom of section or entire map
                                        if(xLimit==colsLimit-1||xLimit==columns-1||i==columns-1){//if last column of section or entire map
                                            arrow = stopSign;
                                            }//end right edge check
                                        else if (columns == colsLimit+1 && i == columns-1   ){//if theres only one column on next section
                                            arrow = stopSign;   
                                        }//end check for single extra column     
                                    }//end bottom edge check
                                } else {//if even row
                                    if (yLimit==rowsLimit-1 || j==rows-1){//if even bottom of section or entire map
                                        if (xLimit == 0 || i == 0){ //if first column of section or entire map
                                            arrow = stopSign;   
                                        }//end left edge check
                                    }//end bottom edge check
                                }//end current row odd even check
                            }  else {

                            }

                        if (+width < 35 || +height < 35){ //tile size check
                            ctx.font = "8px Helvetica";
                            ctx.fillText(arrow,xStart+20,yStart+10); //small
                        } else if (+width < 54 || +height < 54){ //tile size check
                            ctx.font = "10px Helvetica";
                            ctx.fillText(arrow,xStart+25,yStart+15); //med
                        } else if (+width < 64 || +height < 64){ //tile size check
                            ctx.font = "14px Helvetica";
                            ctx.fillText(arrow,xStart+30,yStart+20); //med
                        } else if (+width < 110 || +height < 110){ //tile size check
                            ctx.font = "24px Helvetica";
                            ctx.fillText(arrow,xStart+40,yStart+30); //med
                        } else {
                            ctx.font = "30px Helvetica";
                            ctx.fillText(arrow,xStart+80,yStart+40); //large
                        }//end size check
                        }//end of data draw check

//                      #### Draw Info
                        if (drawInfo) { //draw info tile if checkbox checkes
                            function numberWithCommas(x) {
                                return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
                            }

                            tileCount = (+columns * +rows);                         
                            var hRes = (+width * +columns);
                            var vRes = (+height * +rows);
                            var tPix = (hRes*vRes);

                            var info1 = "Total Resolution : "+hRes+" x "+vRes+". Total tiles : " +tileCount+ ". ("+numberWithCommas(tPix)+") pixels";
                            var info2 = "Single Tile Resolution : "+width+" x "+height;
                            var info3 = "Maximum Output Resolution : "+resWidthLimit+" x "+resHeightLimit+". Max tiles per output : "+(colsLimit*rowsLimit)+" tiles.";
                            var info4 = "Required Outputs : "+outputsWide*outputsHigh;
                            var info5 ="";
                            if (drawUser){
                                var info5 = $('#userText').val();
                            }

                            var rectHeight = 200;
                            var rectWidth = 600;
                            var rectX = leftMarg;
                            var rectY = topMarg;
                            var topMarg = (vRes/2)-(rectHeight/2);
                            var leftMarg = (hRes/2)-(rectWidth/2);
                            ctx.fillStyle =infoBackgroundColor;

                            ctx.fillRect(leftMarg,topMarg,rectWidth,rectHeight);

                            ctx.fillStyle=infoForegroundColor;
                            ctx.font = "12px Lucida Console";
                            ctx.textAlign="center"; 

                            ctx.font = "14px Lucida Console";
                            ctx.fillText(info5,rectX+(rectWidth/2),rectY+(rectHeight/2)-14);
                            ctx.font = "12px Lucida Console";
                            rectY = rectY+14;//new line
                            ctx.fillText(info1,rectX+(rectWidth/2),rectY+(rectHeight/2)-12);
                            rectY = rectY+14;//new line
                            ctx.fillText(info3,rectX+(rectWidth/2),rectY+(rectHeight/2)-12);
                            rectY = rectY+14;//new line
                            ctx.fillText(info2,rectX+(rectWidth/2),rectY+(rectHeight/2)-12);
                            rectY = rectY+14;//new line
                            ctx.fillText(info4,rectX+(rectWidth/2),rectY+(rectHeight/2)-12);
                            ctx.textAlign="left";//reset text alignment for tile coords
                        }


                        console.log("################################");
                        console.log("#      Begin Drawing Tile " + alphabet[xLimit] + (+yLimit+1));
                        console.log("#  Tile Unique # " + counter + ", First Pixel (top-left) : (" + xStart + ", " + yStart + ")");
                        console.log("#  Odd or Even : " + oddOrEven);
                        console.log("#  xLimit is : " + xLimit);
                        console.log("#  yLimit is : " + yLimit);
                        console.log("#  Tile Coords (x, y) (i, j) : (" + i + ", " + j +")");
                        console.log("#  Columns : " + columns);    
                        console.log("#  Rows : " + rows);
                        console.log("#  rowsLimit is : " + rowsLimit);
                        console.log("#  colsLimit is : " + colsLimit);
                        console.log("#  Output Coords (x, y) : (" + (l+1) + ", " + (k+1) + ")");
                        console.log("#  Outputs high : " + outputsHigh);    
                        console.log("#  Outputs wide : " + outputsWide);
                        console.log("#  Total Outputs Needed : " + outputsHigh*outputsWide);
                        console.log("#  Top Edge : " + topEdge);
                        console.log("#  Bottome Edge : " + bottomEdge);
                        console.log("#  Left Edge : " + leftEdge);
                        console.log("#  Right Edge : " + rightEdge);
                        console.log("#  yLimit is : " + yLimit);
                        console.log("#  Output Top Edge : " + outTopEdge);
                        console.log("#  Output Bottom Edge : " + outBottomEdge);
                        console.log("#  Output Left Edge : " + outLeftEdge);
                        console.log("#  Output Right Edge : " + outRightEdge);
                        console.log("#  Map Column is Odd? : " + columnsOdd);
                        console.log("#  Map Row is Odd? : " + rowsOdd);
                        console.log("#  Output Column is Odd? : " + outColumnsOdd);
                        console.log("#  Output Row is Odd? : " + outRowsOdd);



                        xStart = +xStart + +width; //shift starting coords for next column
                    }//end columns for
                    xStart = +colsLimit*l * +width;  //reset x coord to left most side of current output for next row
                    yStart = +yStart + +height;  //shift starting coords for next row
                }//end rows for
            }//end outputs high check(k)
        }//end outputs wide check(l)
    }//end tilesByOutput

    //jQuery
    $('.pixelPerfButton').click(function(){
        var $this = $(this);
        $this.toggleClass('btn-danger').toggleClass('btn-primary');
        $('#canvas').toggleClass('pixelPerf');
        if($this.hasClass('btn-danger')){
            $this.text('Pixel Perfect Preview : On');           
        } else {
            $this.text('Pixel Perfect Preview : Off');
        }
    });

    $('ul#tilePresets li').click(function(){
        var $this = $(this);
        var preWidth= $this.text().substr(0, $this.text().indexOf('x')); 
        var preHeight= $this.text().substr($this.text().indexOf("x") + 1); 
        $('#width').val(preWidth);
        $('#height').val(preHeight);
        });

    $('ul#outputPresets li').click(function(){
        var $this = $(this);
        var preResWidth= $this.text().substr(0, $this.text().indexOf('x')); 
        var preResHeight= $this.text().substr($this.text().indexOf("x") + 1); 
        $('#resWidthLimit').val(preResWidth);
        $('#resHeightLimit').val(preResHeight);
        });

    //col slider
    $('#colSlide').slider({
            tooltip: 'show',
            min: 1,
            max: 120,
            value: $('#columns').val()
        });

    var originalVal;

    $('#colSlide').slider().on('slideStart', function(ev){
        originalVal = $('#colSlide').data('slider').getValue();
    });

    $('#colSlide').slider().on('slideStop', function(ev){
        var newVal = $('#colSlide').data('slider').getValue();
        if(originalVal != newVal) {
            $('#columns').val($(this).val());
        }
        columns=$('#columns').val(); //.value converts input field int to string***
        outputsWide = Math.ceil(columns/colsLimit);  
        draw();
    });

    //row slider
    $('#rowSlide').slider({
            tooltip: 'show',
            min: 1,
            max: 60,
            value: $('#rows').val()
        });

    var originalVal2;

    $('#rowSlide').slider().on('slideStart', function(ev){
        originalVal2 = $('#rowSlide').data('slider').getValue();
    });

    $('#rowSlide').slider().on('slideStop', function(ev){
        var newVal = $('#rowSlide').data('slider').getValue();
        if(originalVal2 != newVal) {
            $('#rows').val($(this).val());
        }
        rows=$('#rows').val(); //.value converts input field int to string***
        outputsHigh = Math.ceil(rows/rowsLimit); 
        draw();
    });

});//main function

JS Fiddle link : http://jsfiddle.net/ganLf56k/

\$\endgroup\$

migrated from stackoverflow.com Jan 20 '17 at 11:31

This question came from our site for professional and enthusiast programmers.

1
\$\begingroup\$

To do the flow you can define some simple directions and then just iterate following the instructions.

For example the human readable form. Start top left, move right until edge,turn down move one step, move left until edge,move down one step, and repeat. Stop when no more places to move.

And the machine readable form

right : 0,
down : 1,
left : 2,
up : 3,
dirs : [[1,0],[0,1],[-1,0],[0,-1]],
map1 : { // top left start rigth, down, left, down 
    x : 0, y : 0,
    directions : [[0,Infinity],[1,1],[2,Infinity],[1,1]],
},

See code for more details.

You put it in code of course with instructions as indexes into various arrays.

THe best way to explain is in code so take a look at the source of the demo. The flow function walks the leds following the instruction in flows.map1 (map1 - map4) You can add other maps but you must make sure that you do not create an endless path. Or you can add an additional check for each step. If the current led.flow !== flows.none then you must be repeating so break and exit. It should not happen so I did not include it, but you may make an error entering data and the page will be blocked.

Demo draws grid waits a second then draw flow. Waits 4 seconds and repeats using the next flow directions. Circle marks start of flow and cross the end.

Note code uses ES6 you must adapt it to ES5 if you want it to work on legacy browsers.

/** CreateImage.js begin **/
    // creates a blank image with 2d context
    var createImage=function(w,h){var i=document.createElement("canvas");i.width=w;i.height=h;i.ctx=i.getContext("2d");return i;}
    
    /** CreateImage.js end **/
    var canvas = createImage(512,512);
    var ctx = canvas.ctx;
    document.body.appendChild(canvas);
    
    // cell size
    var size = 40;
    
    // constants for directions
    const flows = {
        none : -1,
        right : 0,
        down : 1,
        left : 2,
        up : 3,
        end : 4,  // for end of flow
        start : 5,  // for start
        dirs : [[1,0],[0,1],[-1,0],[0,-1]], // x,y step for each direction
        // Maps are used to workout the flow layout
        // x,y is the starting position with 0 as top or left and 1 as bottom or right
        // directions has an array for each direction. the first item is the travel
        // direction and the second item is the max number of steps befor turning
        map1 : { // top left start rigth, down, left, down 
            x : 0, y : 0,
            directions : [[0,Infinity],[1,1],[2,Infinity],[1,1]],
        },
        map2 : {  // top right start, left, down, right, down 
            x : 1, y : 0,
            directions : [[2,Infinity],[1,1],[0,Infinity],[1,1]],
        },
        map3 : {
            x : 0, y : 1, // bottom left start, up, right, down, right
            directions : [[3,Infinity],[0,1],[1,Infinity],[0,1]],
        },
        map4 : {
            x : 1, y : 1, // bottom right start, up, left, down, left
            directions : [[3,Infinity],[2,1],[1,Infinity],[2,1]],
        },
    }
    // colours
    const col1 = "#FA6"
    const col2 = "#6AF"
    // array of leds
    var leds = [];
    // number columns and rows
    var rows;
    var cols;
    // reset or setup leds
    function reset(leds){
        leds.length = 0;
        cols = Math.floor(canvas.height / size);
        rows = Math.floor(canvas.width / size);
        for(var y = 0; y < canvas.height-size; y+= size){
            for(var x = 0; x < canvas.width-size; x += size){
                var c = (Math.floor(y / size) % 2 + Math.floor(x / size) % 2) % 2;
                leds.push({
                    x,y,size,
                    flow : flows.none,
                    col : c ? col1 : col2,
                })
    
            }    
        }
    }
    // Creates flow map for leds. flow maps are in flows as flow.map1, map2... etc
    function createFlow(leds,flowMap){
        var i = 0;
        var prevLed, led;
        var count = 0;
        var x = flowMap.x * (cols-1);
        var y = flowMap.y * (rows-1);
        var moving = true;
        while(moving){
            var ind = x + y * cols;
            prevLed = led;
            var led = leds[ind];
            var dir = flowMap.directions[i % flowMap.directions.length];
            var nx = x + flows.dirs[dir[0]][0];
            var ny = y + flows.dirs[dir[0]][1];
            if(nx >= cols || nx < 0 || ny >= rows || ny < 0 || count >= dir[1]){
                i += 1;
                dir = flowMap.directions[i % flowMap.directions.length];
                nx = x + flows.dirs[dir[0]][0];
                ny = y + flows.dirs[dir[0]][1];
                if(nx >= cols || nx < 0 || ny >= rows || ny < 0){
                    led.flow = flows.end;
                    break;
                }
                count = 0;
            }
            if(led === undefined){
                prevLed.flow = flows.end;
                break;
            }
            if(prevLed === undefined){
                led.flow = flows.start;   
            }else{
                led.flow = dir[0];
            }
            count += 1;
            x = nx;
            y = ny;
        }
    }
    
    // draws the leds
    function draw(leds){
        function arrow(led){
            if(led.flow === flows.none){
                return;
            }
            var step = led.size * (1/4);
            var x = led.x + led.size -step;
            var y = led.y + step
            ctx.strokeStyle = "white";
            ctx.lineWidth = 2;
            ctx.beginPath();
            if(led.flow === flows.end){
                ctx.setTransform(1,0,0,1,x,y);
                ctx.moveTo(-step / 2, -step / 2);
                ctx.lineTo(step / 2, step / 2);
                ctx.moveTo(step / 2, -step / 2);
                ctx.lineTo(-step / 2, step / 2);
                
                ctx.stroke();
                ctx.setTransform(1,0,0,1,0,0);
                return;
            }else if(led.flow === flows.start){
                ctx.setTransform(1,0,0,1,x,y);
                ctx.moveTo(step / 2, 0);
                ctx.arc(0,0, step / 2,0,Math.PI * 2);
                ctx.stroke();
                ctx.setTransform(1,0,0,1,0,0);
                return;
            }else if(led.flow === flows.right){
                ctx.setTransform(1,0,0,1,x,y);
            } else if(led.flow === flows.left){
                ctx.setTransform(-1,0,0,1,x,y);
            } else if(led.flow === flows.down){
                ctx.setTransform(0,1,1,0,x,y);
            } else if(led.flow === flows.up){
                ctx.setTransform(0,-1,1,0,x,y);
                //ctx.setTransform(0,1,-1,0,0,0);
            }
            ctx.moveTo(-step / 2, 0);
            ctx.lineTo(step / 2, 0);
            ctx.moveTo(step / 4, -step / 4);
            ctx.lineTo(step / 2, 0);
            ctx.lineTo(step / 4, step / 4);
            ctx.stroke();
            ctx.setTransform(1,0,0,1,0,0);
        }
        ctx.clearRect(0,0,canvas.width,canvas.height)
        var i = 0;
        while(i < leds.length){
            var l = leds[i++];
            ctx.fillStyle = l.col
            ctx.fillRect(l.x,l.y,l.size,l.size);
            arrow(l);
        }
    }
    
    var flowMaps = [flows.map1,flows.map2,flows.map3,flows.map4];
    var currentFlow = 3;
    function drawAll(){
        reset(leds);
        draw(leds);
        setTimeout(setFlow,1000);
    }
    function setFlow(){
        createFlow(leds,flowMaps[(currentFlow++) % flowMaps.length]);
        draw(leds);
        setTimeout(drawAll,4000);
    }
    drawAll();

\$\endgroup\$
  • \$\begingroup\$ Thanks! This perspective/knowledge really helped me over a hurdle. \$\endgroup\$ – whoismikesmith Jan 23 '17 at 2:29
  • \$\begingroup\$ I need help setting the limit of the flowmap to a be contained within a section of the entire canvas. In other words, I need the ability the have multiple flows within the same over-all map. I'm having trouble adjusting the limits of the flow's boundaries from where they are now (bounded by the entire canvas size) to where I want them to be (bounded by a user-defined output limit in pixels) \$\endgroup\$ – whoismikesmith Feb 16 '17 at 23:04
  • \$\begingroup\$ Here's a link to my latest jsFiddle Altering the vars for tile, output and wall should help illustrate the result I'm after. \$\endgroup\$ – whoismikesmith Feb 16 '17 at 23:11

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.