4
\$\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/

marc_s
1884 silver badges8 bronze badges
asked Jan 20, 2017 at 0:59
\$\endgroup\$
1

1 Answer 1

2
\$\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();

answered Jan 20, 2017 at 10:32
\$\endgroup\$
3
  • \$\begingroup\$ Thanks! This perspective/knowledge really helped me over a hurdle. \$\endgroup\$ Commented Jan 23, 2017 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\$ Commented Feb 16, 2017 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\$ Commented Feb 16, 2017 at 23:11

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.