Follow @Ibrahim
Tweet
BLOB.jsv1.0.4.2
Drawing shapes and rendering scenes..
Below are the quick links: INTRODUCTION RENDERING SHAPES
This part is an expansion blob.js and illustrates the use of helper functions.
With the 1.0.4.2 version, a new global object is introduced: blobTools. This object has inside a set of functions to be used as drawing API.
Let's see some examples. First include the script in your heading:
script src="http://i-pv.org/js/blob1.0.4.2.js" type="text/javascript.
The first function we will take a look at is the wiggle function. Access is like blobTools.wiggle. In the below examples, I will only indicate the parameter or parameterTime key of the objects passed to blob function. The first six examples have a new key "isStatic" set to 1, which means the window.requestAnimationFrame is not called after initial rendering. This should save you some computation.
The wiggle function takes 5 arguments, the r1,r2,teethCount,i and nodeCount. You pass i and nodeCount as they are. r1 is the lowest radius, r2 is the peak of a teeth, and the teethcount is the number of teeth and gaps. So a teethcount of 12 will yield 6 teeths. The 12 fucntions used to draw the above shapes in order (left to right) are below:
1. blob{nodeCount:60,..isStatic:1}
2. blob{nodeCount:36,..,isStatic:1,parameter:function(i,nodeCount){return 1+blobTools.wiggle(1,1.2,12,i,nodeCount);}}
3. blob{nodeCount:72,..,isStatic:1,parameter:function(i,nodeCount){return 1+blobTools.wiggle(1,1.2,12,i,nodeCount);}}
4. blob{nodeCount:72,..,isStatic:1,parameter:function(i,nodeCount){return 1+blobTools.wiggle(1,0.9,12,i,nodeCount);}}
5. blob{nodeCount:30,..,isStatic:1,parameter:function(i,nodeCount){return 1+blobTools.wiggle(1,1.5,20,i,nodeCount);}}
6. blob{nodeCount:12,..,isStatic:1,parameter:function(i,nodeCount){return 1+blobTools.wiggle(1,1.5,6,i,nodeCount);}}
7. blob{nodeCount:48,..,isStatic:1,parameter:function(i,nodeCount){return 1+blobTools.wiggle(1,1.25,10,i,nodeCount);}}
8. blob{nodeCount:48,..,isStatic:1,parameter:function(i,nodeCount){return 1+blobTools.wiggle(1,0.1,6,i,nodeCount);}}
9. blob{nodeCount:72,..harmonicRadius:0.0001,isStatic:1,parameterTime:function(i,nodeCount,t){t = t*1000; var j= i+(t%4000)/4000*nodeCount; return 1+blobTools.wiggle(1,1.2,12,j,nodeCount);}}
10. blob{nodeCount:72,..harmonicRadius:0.0001,isStatic:1,parameterTime:function(i,nodeCount,t){t = t*1000; var j= i+nodeCount-(t%4000)/4000*nodeCount; return 1+blobTools.wiggle(1,1.2,12,j,nodeCount);}}
11. blob{nodeCount:72,..harmonicRadius:15,isStatic:1,parameterTime:function(i,nodeCount,t){t = t*1000; var j= i+nodeCount-(t%4000)/4000*nodeCount; return 1+blobTools.wiggle(1,1.2,12,j,nodeCount);}}
12. blob{nodeCount:72,..harmonicRadius:15,isStatic:1,parameterTime:function(i,nodeCount,t){t = t*1000; var j= i+nodeCount-(t%4000)/4000*nodeCount; var Rx = (t%4000)>2000?1.5-0.5*(t%2000)/2000:1+0.5*(t%2000)/2000; return 1+blobTools.wiggle(1,Rx,12,j,nodeCount);}}


The next helper function is the star function. The arguments you use for it are vertexLengh (0-1), vertexCount, sharpness (1-0), i and nodeCount. As always you pass the i and nodeCount as they are to parameter or parameterTime fileds of the blob function. Below are some examples:
The 8 functions that are used above from left to right are below:
1.blob({nodeCount:30,..,isStatic:1,parameter:function(i,nodeCount){return blobTools.star(0.1,5,0.5,i,nodeCount);}});
2.blob({nodeCount:30,..isStatic:1,parameter:function(i,nodeCount){return blobTools.star(0.35,5,0.5,i,nodeCount);}});
3.blob({nodeCount:30,..isStatic:1,parameter:function(i,nodeCount){return blobTools.star(0.5,10,0.5,i,nodeCount);}});
4.blob({nodeCount:30,..isStatic:0,parameter:function(i,nodeCount){return blobTools.star(0.5,10,0.5,i,nodeCount);}});
5.blob({nodeCount:60,..isStatic:0,parameterTime:function(i,nodeCount,t){t = t*1000; var j = i+(t%10000)/10000*nodeCount; return blobTools.star(0.5,10,0.5,j,nodeCount);}});
6.blob({nodeCount:60,..isStatic:0,parameterTime:function(i,nodeCount,t){t = t*1000; var j = i+nodeCount-(t%10000)/10000*nodeCount; return blobTools.star(0.5,10,0.5,j,nodeCount);}});
7.blob({nodeCount:30,..isStatic:0,parameter:function(i,nodeCount){return blobTools.star(1.5,5,0.5,i,nodeCount);}});
8.blob({nodeCount:30,..isStatic:0,parameterTime:function(i,nodeCount,t){var t = t*1000; var j = i+(t%10000)/10000*nodeCount; return blobTools.star(1.5,5,0.5,j,nodeCount);}});
The next helper function is the vBelt function. The vBelt function is drawn by 2 circles (gears) with the drawing origin being the left one. It accepts 5 arguments, r the left gear radius, R the right gear radius, d the distance between the two gears, i and nodeCount. Due to the usage of Math.round to calculate node lengths, you might run into irregular shapes, if this happens try changing the nodeCount value. By default try using 4k+1 amount of nodes where k is a positive integer. When using parameter time, make sure that i does not exceed nodeCount. For instance instead of i+(t%X)/X*nodeCount use (i+(t%X)/X*nodeCount)%nodeCount. Below are some examples:
Let's see the functions now:
1.blob({nodeCount:30,..,isStatic:1,parameter:function(i,nodeCount){return blobTools.vBelt(1,2,1,i,nodeCount);}});
2.blob({nodeCount:30,..,isStatic:1,parameter:function(i,nodeCount){return blobTools.vBelt(1,2,0,i,nodeCount);}});
3.blob({nodeCount:30,..,isStatic:1,parameter:function(i,nodeCount){return blobTools.vBelt(1,1,0,i,nodeCount);}});
4.blob({nodeCount:30,..,isStatic:1,parameter:function(i,nodeCount){return blobTools.vBelt(2,1,0,i,nodeCount);}});
5.blob({nodeCount:30,..,isStatic:0,parameterTime:function(i,nodeCount,t){var t = t*1000;var j = (i+(t%4000)/4000*nodeCount)%nodeCount;return blobTools.vBelt(2,1,0,j,nodeCount);}});
6.blob({nodeCount:30,..,isStatic:0,parameterTime:function(i,nodeCount,t){var t = t*1000;var r = (t%4000)>2000? 3-(t%2000)/1000:1+(t%2000)/1000; var R = (t%4000) < 2000?2-(t%2000)/2000:1+(t%2000)/2000;var d = (t%4000) < 2000?-2+(t%2000)/1000:-(t%2000)/1000;var j = (i+(t%4000)/4000*nodeCount)%nodeCount;return blobTools.vBelt(r,R,d,j,nodeCount);}});
7.blob({nodeCount:30,..,isStatic:0,parameterTime:function(i,nodeCount,t){var t = t*1000;var r = (t%4000)>2000? 3-(t%2000)/1000:1+(t%2000)/1000; var R = (t%4000) < 2000?2-(t%2000)/2000:1+(t%2000)/2000;var d = (t%4000) < 2000?-2+(t%2000)/1000:-(t%2000)/1000;var j = (i+(t%4000)/4000*nodeCount)%nodeCount;return blobTools.vBelt(r,R,d,j,nodeCount);}});
8.blob({nodeCount:144,..,isStatic:0,parameterTime:function(i,nodeCount,t){t=t*1000;var d = (t%4000) < 2000?-2+(t%2000)/1000:-(t%2000)/1000;var j = (i+(t%4000)/4000*nodeCount)%nodeCount;return blobTools.vBelt(1,2,d,i,nodeCount)+blobTools.wiggle(1,1.2,48,j,nodeCount);}});
This time we will draw an ellipse with the blobTools.ellipse. It accepts 3 compulsory arguments: a (the horizontal axis, normalized 0-x where x is a positive integer), i and nodeCount. b can be supplied as the 4th argument as vertical axis, but is already known when you supply radius with the blob object. Supply b when you want it to be different from the radius.
Here are the 4 functions for the above ellipses:
1.blob({nodeCount:40,..,isStatic:1,parameter:function(i,nodeCount){return blobTools.ellipse(2,i,nodeCount);}});
2.blob({nodeCount:40,..,isStatic:1,parameter:function(i,nodeCount){return blobTools.ellipse(2,i,nodeCount,0.5);}});
3.blob({nodeCount:40,..,isStatic:1,parameter:function(i,nodeCount){return blobTools.ellipse(2,i,nodeCount,2);}});
4.blob({nodeCount:40,..,isStatic:0,parameterTime:function(i,nodeCount,t){t=t*1000;var a = (t%4000) < 2000?0.5+(t%2000)/2000*1.5:2-(t%2000)/2000*1.5;var b = (t%4000) < 2000?2-(t%2000)/2000*1.5:0.5+(t%2000)/2000*1.5;return blobTools.ellipse(a,i,nodeCount,b);}});
The next function is blobTools.heart. It is not exactly a cardoid, the length of the nodes for the top part follows a sigmoid curve whereas the bottom part is an ellipse. The function takes 4 arguments, a, the horizontal radius of the ellipse, b, the height of the atria, i and nodeCount. Your node count has to be 4k+1 where k is a positive integer. Below are some examples:
1.blob({nodeCount:41,..,isStatic:1,parameter:function(i,nodeCount){return blobTools.heart(1,3,i,nodeCount);}});
2.blob({nodeCount:41,..,isStatic:1,parameter:function(i,nodeCount){return blobTools.heart(2,1.5,i,nodeCount);}});
3.blob({nodeCount:41,..,isStatic:0,parameter:function(i,nodeCount){return blobTools.heart(1,2.5,i,nodeCount);}});
4.blob({nodeCount:41,..,isStatic:0,parameterTime:function(i,nodeCount,t){t=t*1000;var a = (t%4000) < 2000?1.5-(t%2000)/2000:0.5+(t%2000)/2000;var b = (t%4000) < 2000?1+(t%2000)/2000*2:3-(t%2000)/2000*2;return blobTools.heart(a,b,i,nodeCount);}});
You have probably seen this one from the previous section. The droplet function accepts 3 arguments. Roundness (0-1), i and nodeCount. Here are some examples:
1.blob({nodeCount:40,..,isStatic:0,parameter:function(i,nodeCount){return blobTools.droplet(1,i,nodeCount);}});
2.blob({nodeCount:40,..,isStatic:0,parameter:function(i,nodeCount){return blobTools.droplet(0.75,i,nodeCount);}});
3.blob({nodeCount:40,..,isStatic:0,parameter:function(i,nodeCount){return blobTools.droplet(0.5,i,nodeCount);}});
4.blob({nodeCount:40,..,isStatic:0,parameter:function(i,nodeCount){return blobTools.droplet(0,i,nodeCount);}});
5.blob({nodeCount:40,..,isStatic:0,parameterTime:function(i,nodeCount,t){t=t*1000; var roundness= (t%10000)>5000?1-(t%5000)/5000:(t%5000)/5000; return blobTools.droplet(roundness,i,nodeCount);}});
The following function is the one I like the most. Although you can replicate some examples with the previous functions, polygonAngular function gives you bit more control. I always thought expressing a polygon with radial coordinates r, ϑ is more natural for the human brain to conceive than simply writing path attribute like MoveTo X,Y LineTo U,W CurveTo.. In this function you define a series of angles < 180 and distances in units of r. For instance [30,2,60,1,90,1,100,3] means rotate about the origin clockwise 30 degrees starting from 12 o'clock position with 2 radius(remember, you already specified this in the blob function) and then take 60 degrees from where you left with 1 radius and so on. The animations perhaps can be replicated in SVG with by changing the transform attribute but you won't get the warp effect. The nodeCount parameter is completely free from the array representation of the polygon which is a very powerful aspect of this function. The origin has to be inside the area covered by the polygon. This means that when you specify an array where the angles do not addup to 360 degrees, the rest of the joint is assumed such that if the line connecting the first and the last radius you specified does not engulf the origin, than the triangle formed by r1,origin and rLast is mirrored with respect to origin and the path is closed. Check some examples below, it will become more clear:
1.blob({nodeCount:60,..,isStatic:0,parameter:function(i,nodeCount){return blobTools.polygonAngular([60,1,60,2,60,1,60,2,60,1,60,2],i,nodeCount);}});
2.blob({nodeCount:60,..,isStatic:0,parameter:function(i,nodeCount){return blobTools.polygonAngular([45,Math.sqrt(2),90,Math.sqrt(2),90,Math.sqrt(2),90,Math.sqrt(2)],i,nodeCount);}});
3.blob({nodeCount:60,..,isStatic:0,parameter:function(i,nodeCount){var j = (i+nodeCount/4)%nodeCount; return blobTools.polygonAngular([60,1,60,1,60,1,60,1,60,1,60,1],j,nodeCount);}});
4.blob({nodeCount:60,..,isStatic:0,parameter:function(i,nodeCount){var A = 1/Math.tan(54/360*2*Math.PI)/Math.tan(17/360*2*Math.PI);return blobTools.polygonAngular([36,1,36,A,36,1,36,A,36,1,36,A,36,1,36,A,36,1,36,A],i,nodeCount);}});
5.blob({nodeCount:60,..,isStatic:0,parameter:function(i,nodeCount){var j = (i+nodeCount/360*144)%nodeCount;var A = 1/Math.tan(54/360*2*Math.PI)/Math.tan(17/360*2*Math.PI);return blobTools.polygonAngular([36,1,36,A,36,1,36,A,36,1,36,A,36,1],j,nodeCount);}});
6.blob({nodeCount:60,..,isStatic:0,parameter:function(i,nodeCount){return blobTools.polygonAngular([45,Math.sqrt(2),90,Math.sqrt(2),90,Math.sqrt(2)],i,nodeCount);}});
7.blob({nodeCount:60,..,isStatic:0,parameter:function(i,nodeCount){return blobTools.polygonAngular([60,1,60,1,60,1,60,1],i,nodeCount);}});
8.blob({nodeCount:60,..,isStatic:0,parameter:function(i,nodeCount){return blobTools.polygonAngular([45,Math.sqrt(2),45,1,45,Math.sqrt(2),45,1,45,Math.sqrt(2),45,1,45,Math.sqrt(2),45,(1+Math.sqrt(3))],i,nodeCount);}});
Now, you might wonder why we do all this that are purely based on shape rendering. I think the most important part visualizing data is tackling the shape geometry and the transition of that geometry to another geometry. If you can overcome these two aspects, the rest of visualization is ordering shape elements in a way that resembles your data. Here is a proof of concept. You have seen me previously in this page drawing stars, houses, polygons etc. You might at first think this gibberish has nothing to do with visualization or protein sequence etc. However I think that you can't draw something more complex other than a house unless you can draw the house in the first place. Take the polygonAngular function for instance, I define a function such as:
function XYtoRtheta(array) { var xValues = [];//initiate an array for x values only var yValues = [];//initiate an array for y values only var xyHashes = [];//keep all your x,y value pairs in an object for (var i = 0;i < array.length;i+=2){ xValues.push(array[i]); yValues.push(array[i+1]); xyHashes.push({x:array[i],y:array[i+1]}); } xyHashes.sort(function(a,b){return a.x-b.x;});//sort it based on increasing x values var xMax = Math.max.apply(null,xValues);//find maximum x var yMax = Math.max.apply(null,yValues);//find maximum y var resultArray = [];//initiate an array where you store result pairs for (var i = 0,angleSum=0;i < xyHashes.length;i++){ var xConverted = xyHashes[i].x/xMax*360-angleSum; var yConverted = 3+xyHashes[i].y/yMax;//y values with an offset of 3 radius angleSum = xyHashes[i].x/xMax*360; resultArray.push(xConverted,yConverted); } return resultArray; } You will also need a dataset to use this function. Let's generate a random dataset of 100 points:
function generateRandomPoints(amount){ var array = []; for (var i=0;i < amount;i++) { array.push(Math.random(),Math.random()); } return array; } We will use the functions like this: blob{...,parameter:function(i,nodeCount){return blobTools.polygonAngular(XYtoRtheta(generateRandomPoints(100)),i,nodeCount)}}.Here is the result with a simulation dataset of 100 points:
The canvas elements above are under the drawing area since their z indexes are smaller. This means the mouse-over coordinates are exposed, so you can further elaborate the graph and add descriptions for each data point etc..You can pepper and salt this representation with glow effect, transitions etc. We were also lucky to get the rounding function for free by changing the nodeCount value:))) In the next sections I will try concentrate on animating origins and rendering scenes.
I had a lot of fun designing these examples, I hope you had fun too. If you find these tutorials useful, I would appreacite if you click on the like buttons in the beginning of the page. If you want to cite this work, you can use this DOI: . Thank you and merry christmas.
Ibrahim Tanyalcin