Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Initial Commit

  • Loading branch information...
commit 7ef2802ef75d59f5464a8f863e43da4e423c6fd1 0 parents
@imbcmdth authored
3  README
@@ -0,0 +1,3 @@
+R-Tree Library for Javascript
+
+TODO: Make More ReadMe
3  examples/20k_tree.js
3 additions, 0 deletions not shown
480 examples/canvas_test_generate.html
@@ -0,0 +1,480 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>More Canvas Fun</title>
+<script type='text/javascript' src='../libs/firebug-lite-compressed.js'></script>
+<script type='text/javascript' src="../libs/jquery.min.js"></script>
+<script type='text/javascript' src="../src/rtree.js"></script>
+</head>
+<body>
+ This test generate 20k squares over a an area several times larger than the canvas window. Once insertion has completely, the canvas begins scrolling diagonally. This test shows canvas rendering and event handling of a small subset (<300 objects) of a much larger dataset (20k objects) at interactive rates.<br/>
+ <canvas id="game" width="650px" height="500px">
+ </canvas>
+ <script type="text/javascript">
+
+ /* I want to be able to do:
+ * cQuery:
+ * addObject(object_interface)
+ *
+ *
+ * cQuery(Rectangle | CanvasObject) [decorated array]
+ *
+ * object interface:
+ * attachEvent(event_name,function())
+ * removeEvent(event_name)
+ * doAreaEvent(event_name)
+ *
+ */
+/********************************************
+ * Full Canvas Library *
+ ********************************************/
+
+var canvwrap=(function(){
+
+ var canv=null,
+ canvctx=null,
+ rt=new RTree(),
+ layers={'main':0},
+ mouse={track:false,x:0,y:0},
+ mouseouts=[],
+ mouseoutids=0;
+
+ var canvasElement=function(x,y,w,h){
+ this.x=x;
+ this.y=y;
+ this.w=w;
+ this.h=h;
+ }
+
+ canvasElement.prototype.attachEvent=function(action,func,layer){
+ canvwrap.attachEvent({x:this.x,y:this.y,w:this.w,h:this.h},action,func,layer);
+ }
+
+ canvasElement.prototype.toString=function(){ return '[CanvasElement]'; };
+
+ function addIndex(bounds,id,action,func,layer, opts)
+ {
+ if(!func) return;
+ if(!layer) layer='main';
+
+ // Get all overlapping objects from the tree
+ var local_objs = treeSearch(bounds.x,bounds.y,bounds.w,bounds.h);
+ var new_obj = true;
+
+ // Look for an existing object with our same ID
+ while(local_objs.length > 0)
+ {
+ obj = local_objs.pop();
+ if(obj.id == id)
+ {
+ new_obj = false;
+ break;
+ }
+ }
+ // If no object was found, make a new one...
+ if(new_obj) obj = {"func":{}, "action":{}, "id":id};
+
+ // If no events have been attached to the canvas, attach them..
+ if((action=='mouseover' || action=='mouseout' || action=='mousemove') && !mouse.track)
+ {
+ mouse.track=true;
+ canv.addEvent('mousemove',__mouseMove);
+ }
+
+ for (var k in opts)
+ obj[k] = opts[k];
+
+
+ // Update our obj's properties
+ obj.func[action] = func;
+ obj.action[action] = true;
+ obj.layer = layers[layer];
+ if(new_obj) rt.insert(bounds,obj);
+ }
+
+ function treeSearch(x,y,w,h)
+ {
+ var results=rt.search({x:x,y:y,w:w,h:h});
+ if(results.length==0)
+ return [];
+ results.sort(sortRes);
+ return results;
+ }
+
+ function sortRes(a,b){ return b.layer-a.layer; }
+
+ function __mouseDown(e){
+ var xy=getXY(e);
+ xy.x = canv_off_x+xy.x/zoom;
+ xy.y = canv_off_y+xy.y/zoom;
+
+ var results=treeSearch(xy.x,xy.y,1,1);
+ for(var i=0;i<results.length;i++)
+ if(results[i].action[e.type])
+ results[i].func[e.type].apply(results[i], [{x:xy.x,y:xy.y}]);
+ }
+
+
+ function __drawImage(image, dx, dy)
+ {
+ if(arguments[8])
+ {
+ sx=dx;
+ sy=dy;
+ sw=arguments[3];
+ sh=arguments[4];
+ dx=arguments[5];
+ dy=arguments[6];
+ dw=arguments[7];
+ dh=arguments[8];
+ canvctx.drawImage(image,sx,sy,sw,sh,dx,dy,dw,dh);
+ return new canvasElement(dx,dy,dw,dh);
+ }
+
+ if(arguments[3] && arguments[4])
+ {
+ dw=arguments[3];
+ dh=arguments[4];
+ }
+ else
+ {
+ dw=image.width;
+ dh=image.height;
+ }
+ canvctx.drawImage(image,dx,dy,dw,dh);
+ return new canvasElement(dx,dy,dw,dh);
+ }
+
+ function getXY(e)
+ {
+ var posx = 0;
+ var posy = 0;
+ if (!e) var e = window.event;
+ if (e.pageX || e.pageY) {
+ posx = e.pageX;
+ posy = e.pageY;
+ }
+ else if (e.clientX || e.clientY) {
+ posx = e.clientX + document.body.scrollLeft
+ + document.documentElement.scrollLeft;
+ posy = e.clientY + document.body.scrollTop
+ + document.documentElement.scrollTop;
+ }
+ posx=posx-canv.offsetLeft;
+ posy=posy-canv.offsetTop;
+ return {x:posx,y:posy};
+ }
+
+ function __mouseMove(e){
+ var xy=getXY(e);
+ xy.x = canv_off_x+xy.x/zoom;
+ xy.y = canv_off_y+xy.y/zoom;
+
+ var results=treeSearch(xy.x,xy.y,1,1),
+ thrown=null,
+ entryfound=false;
+ var results_copy = results.slice(0, results.length);
+ var l_mouse_ins = []; // Holds new (mousein) elements
+ var l_mouse_moves = []; // holds all repeated elements
+ // mouseouts will hold mouseouts
+ mouse.x=xy.x;
+ mouse.y=xy.y;
+
+ // Seperate the elements from mouseouts + results into three arrays (in, out, move)
+ while(results.length > 0)
+ {
+ var current_results = results.pop();
+
+ var m=0;
+ while(m<mouseouts.length)
+ {
+ if(mouseouts[m].id === current_results) //IF it was found in "results" then it is a move
+ {
+ l_mouse_moves.push(mouseouts.splice(m));
+ m-=1;
+ current_results = null;
+ }
+ m+=1;
+ }
+ if(current_results) //IF it wasn't found in "results" then it is new
+ l_mouse_ins.push(current_results);
+ }
+
+ // now the three arrays contain the elements they should..
+ while(mouseouts.length>0)
+ {
+ var thrown=mouseouts.pop();
+ if(thrown.action['mouseout'])
+ thrown.func['mouseout']({x:xy.x,y:xy.y});
+ }
+ while(l_mouse_ins.length>0)
+ {
+ var thrown=l_mouse_ins.pop();
+ if(thrown.action['mouseover'])
+ thrown.func['mouseover']({x:xy.x,y:xy.y});
+ }
+ while(l_mouse_moves.length>0)
+ {
+ var thrown=l_mouse_moves.pop();
+ if(thrown.action['mousemove'])
+ thrown.func['mousemove']({x:xy.x,y:xy.y});
+ }
+ mouseouts = results_copy;
+ }
+
+ function init(canvid, width, height)
+ {
+ canv=document.getElementById(canvid);
+ canv.addEvent=function(type,func){
+ if(canv.addEventListener)
+ canv.addEventListener(type,func,false);
+ else if(canv.attachEvent)
+ canv.attachEvent(type,func);
+ };
+
+ canv.addEvent('mousedown',__mouseDown);
+ canv.addEvent('mouseup',__mouseDown);
+ canv.addEvent('click',__mouseDown);
+ canv.addEvent('dblclick',__mouseDown);
+
+ if(canv.getContext)
+ canvctx=canv.getContext('2d');
+ }
+
+ return {
+ init: init,
+ attachEvent: addIndex,
+ drawImage: __drawImage,
+ getTree: function(){return(rt);}
+ }
+
+})();
+
+canvwrap.init("game", 650, 475);
+
+var ctx=document.getElementById('game');
+ctx = ctx.getContext('2d');
+
+function test(dir){
+ //alert(dir);
+ var dirarr=['top_left', 'top', 'top_right', 'right', 'bottom_right', 'bottom', 'bottom_left', 'left'];
+ document.getElementById('status').innerHTML=dirarr[dir] + " was clicked!";
+}
+
+var canv_off_x = 1237;
+var canv_del_x = 0;
+var canv_off_y = 931;
+var canv_del_y = 0;
+var zoom = 1;
+var zdel = 0.99; // speed of zoom
+zdel = 1.0;
+
+var border_width = 4.0;
+
+var fps_timer = false;
+
+function draw_nodes(context)
+{
+ var hit_stack = []; // Contains the local elements (at current tree depth) that overlap
+ var temp_tree = canvwrap.getTree().get_tree();
+
+ context.strokeStyle = "rgb(0,0,0)";
+ context.lineWidth = border_width*zoom;
+
+// hit_stack.push(temp_tree.nodes);
+// context.strokeRect(temp_tree.x, temp_tree.y, temp_tree.w, temp_tree.h);
+
+ var elements = canvwrap.getTree().search({x:canv_off_x,y:canv_off_y,w:context.canvas.width/zoom,h:context.canvas.height/zoom}, true);
+ var to_draw = elements.length;
+if(window["console"] && console.time && fps_timer)
+ console.time("Draw Frame...");
+ while(elements.length >0)
+ {
+ var el = elements.pop();
+ context.strokeRect((el.x-canv_off_x)*zoom, (el.y-canv_off_y)*zoom, el.w*zoom, el.h*zoom);
+ context.fillStyle=el.leaf.color;
+ context.fillRect((el.x-canv_off_x)*zoom, (el.y-canv_off_y)*zoom, el.w*zoom, el.h*zoom);
+ //context.fillText(el.node.id+"", (el.x-canv_off_x+1)*zoom, (el.y-canv_off_y)*zoom);
+ }
+ if(window["console"] && console.timeEnd && fps_timer && !(fps_timer = false))
+ console.timeEnd("Draw Frame...");
+ return(to_draw);
+}
+
+function draw(){
+ var tcanvas = document.getElementById("game");
+ var ctx = tcanvas.getContext("2d");
+
+// ctx.fillStyle="rgba(255,255,255,0.3)";
+// ctx.fillRect(0, 0, tcanvas.width, tcanvas.height);
+ ctx.clearRect(0, 0, tcanvas.width, tcanvas.height)
+
+ var drawn = draw_nodes(ctx);
+ ctx.clearRect(0, 0, 160, 40);
+ ctx.fillStyle="rgb(0,0,0)";
+ ctx.fillText("# of Elements on Screen: " + drawn, 10, 10);
+ ctx.fillText("# of Elements in Tree: " + test_damnit, 10, 20);
+ ctx.fillText("-= " + col_name +" =-", 10, 30);
+}
+
+q=0;
+var test_damnit = 0;
+
+switch(parseInt((Math.random()*7.99)))
+{
+ case 0:
+ col_name="Primarily Mellow";
+ cols =['#B01E1E','#174385','#15592F','#FCDC0A','#000000'];border_width = 3.0;
+ break;
+ case 1:
+ col_name="A Sad Memory";
+ cols =['#1C465C','#597B6B','#D5C263','#E8DF97','#2D88A5'];border_width = 5.0;
+ break;
+ case 2:
+ col_name="Patriot Games";
+ cols =['#ffffff','#353842','#5E6669','#BED1AE','#5E6669','#DEEFBB','#5E6669','#BED1AE','#5E6669','#DEEFBB','#C7493B'];border_width = 3.0;
+ break;
+ case 3:
+ col_name="Weakerthans";
+ cols =['#ffffff', '#292929','#292929','#E2E2E2','#E2E2E2','#E2E2E2','#1A4685','#E80101','#FFBF15'];border_width = 1.0;
+ break;
+ case 4:
+ col_name="Stoicism";
+ cols =['#303030','#303030','#303030','#303030','#303030','#303030','#303030','#303030','#F5EDDF','#F5EDDF','#F5EDDF','#F5EDDF','#F5EDDF','#EDE592','#EDE592','#BFC7C7','#BFC7C7','#D84818']; border_width = 1.0;
+ break;
+ case 5:
+ col_name="Fish bowl";
+ cols =['#BDCCDF', '#BDD1DE', '#BDDEDE', '#BDDED7', '#FC8B4A'];border_width = 1.0;
+ break;
+ case 6:
+ col_name="Bull in a China Shop";
+ cols =['#FDECB1','#F7E5B5','#3A271F','#C51616','#EDDBB4'];border_width = 3.0;
+ break;
+ case 7:
+ col_name="A Funk Odyssey";
+ cols =['#7A6C5D','#7A6C5D','#7A6C5D','#7A6C5D','#7A6C5D','#7A6C5D','#F0B603','#F0B603','#F0B603','#F0B603','#F0B603','#98AAAC','#98AAAC','#98AAAC','#A3033D','#A3033D','#E9FABE'];border_width = 3.0;
+ break;
+}
+
+// LOAD PRE-BUILT TREE
+// canvwrap.getTree().set_tree(OBJ_TREE_20K);
+
+if( canvwrap.getTree().get_tree().nodes.length == 0 )
+{
+ var scale_fact = 1.0;
+
+ var new_func = function(e){alert("You hit object number '" + this.id + "' and the fill color is '"+ this.color);};
+ var do_some_then_yield = function(){
+ var loops = 0;
+ var made = 0;
+ var bounds = {x:(Math.random()*6000+20), y:(Math.random()*4000+7), w:(Math.random()*(100/scale_fact)+1), h:(Math.random()*(100/scale_fact)+1)};
+ var opts = {color:cols[(Math.random()*100).toFixed()%5]};
+ while(test_damnit < 20000)
+ {
+ if(canvwrap.getTree().count({x:bounds.x-border_width-(25/scale_fact), y:bounds.y-border_width-(25/scale_fact), w:bounds.w+border_width*2+(50/scale_fact), h:bounds.h+border_width*2+(50/scale_fact)}) == 0)
+ {
+ canvwrap.attachEvent(bounds,test_damnit,'mousedown', new_func, null, opts);
+ test_damnit++;
+ bounds = {x:(Math.random()*6000+20), y:(Math.random()*4000+7), w:(Math.random()*(100/scale_fact)+1), h:(Math.random()*(100/scale_fact)+1)};
+ var opts = {color:cols[(Math.random()*123).toFixed()%cols.length]};
+ made++;
+ }
+ else
+ {
+ bounds.x=(Math.random()*6000+20); bounds.y=(Math.random()*4000+7); bounds.w=(Math.random()*(100/scale_fact)+1); bounds.h=(Math.random()*(100/scale_fact)+1);
+ }
+ loops++;
+ if(loops >= 500)
+ {
+ if(loops/made > 5)
+ scale_fact *= 2;
+ setTimeout(do_some_then_yield, 0);
+ return;
+ }
+ }
+ canv_del_x = canv_del_y = -2;
+ };
+ do_some_then_yield();
+}
+//canv_del_x = canv_del_y = -2;
+setInterval(function(){ if(zoom > 2 || zoom < 0.4) zdel = 1.0 / zdel; if(canv_off_x > 5400 || canv_off_x < 0) canv_del_x*=-1; if(canv_off_y > 3600 || canv_off_y < 0) canv_del_y*=-1; zoom *= zdel; canv_off_x += (canv_del_x/zoom); canv_off_y += (canv_del_y/zoom); draw();}, 30); // Yield to browser temporarily to suppress "script is taking too damn long!" alert
+
+//ctx.drawImage(document.getElementById('test'), 0, 0, 50, 50, 200, 200, 50, 50); // not possible without an intermediary
+
+
+
+var UI=(function(){
+
+ var arrows=[
+ [img=new Image(10,10),'top_left',2,2,10,10,0],
+ [img=new Image(10,10),'top',14,2,10,10,1],
+ [img=new Image(10,10),'top_right',26,2,10,10,2],
+ [img=new Image(10,10),'right',26,14,10,10,3],
+ [img=new Image(10,10),'bottom_right',26,26,10,10,4],
+ [img=new Image(10,10),'bottom',14,26,10,10,5],
+ [img=new Image(10,10),'bottom_left',2,26,10,10,6],
+ [img=new Image(10,10),'left',2,14,10,10,7]];
+
+ function loadUI(){
+ // arrows for the top are so far implemented
+ var imagessrc="/files/images/game_ui/arrows_new/",
+ imgext="gif";
+
+ for(var i=0;i<arrows.length;i++)
+ {
+ arrows[i][0].src=imagessrc+arrows[i][1]+"."+imgext;
+ arrows[i][0].onload=function(im){return function(){im.loaded=true; loadProgress();}}(arrows[i]);
+ }
+ }
+
+ function loadProgress(){
+ if(!this.loaded)
+ this.loaded=0;
+ this.loaded++;
+
+ if(this.loaded==7)
+ drawArrows();
+ }
+
+ function drawArrows(){
+ /*index=index||-1;
+ if(index>=0)
+ if(arrows[index].loaded){
+ ctx.drawImage(arrows[index][0], arrows[index][2], arrows[index][3]);
+ attEvent.add(function(i){return function(){test(arrows[i][6]);}}(index),arrows[index][2],arrows[index][3],arrows[index][4],arrows[index][5],'ui');
+ }
+ else
+ setTimeout(function(){ drawArrows(arrows,i); }, 45);
+ else
+ */for(var i=0;i<arrows.length;i++)
+ {
+ //if(arrows[i].loaded){
+ x=arrows[i][2];
+ y=arrows[i][3];
+ w=arrows[i][4];
+ h=arrows[i][5];
+ canvwrap.drawImage(arrows[i][0], x, y, w, h).attachEvent('mousedown',function(i){
+ return function(){
+ test(arrows[i][6]);
+ }
+ }(i));
+
+ /*}
+ //else
+ //{
+ setTimeout(function(i){return function(){drawArrows(arrows,i);}}(i), 45);
+ break;
+ */}
+ }
+
+ return {
+ load: loadUI
+ };
+
+})();
+
+//UI.load();
+
+</script>
+</body>
+</html>
26 examples/index.html
@@ -0,0 +1,26 @@
+<html>
+<head>
+ <title>R-Tree Madness</title>
+ <script type='text/javascript' src="../libs/jquery.min.js"></script>
+</head>
+<body>
+<h1>R-Tree for Javascript</h1>
+<form style="float:right;"action="https://www.paypal.com/cgi-bin/webscr" method="post"><input type="hidden" name="cmd" value="_s-xclick"><input type="hidden" name="hosted_button_id" value="10326004"><input type="image" src="https://www.paypal.com/en_US/i/btn/btn_donateCC_LG.gif" border="0" name="submit" alt="PayPal - The safer, easier way to pay online!"><img alt="" border="0" src="https://www.paypal.com/en_US/i/scr/pixel.gif" width="1" height="1"></form>
+<p>This page contains examples of my R-Tree Library (<a href="rtree.js">rtree.js</a>) for Javascript. Check the results of some <a href="unit.html">unit tests</a>.
+</p>
+<h3>Tests:</h3>
+<h4><a href="simple_test.html">Simple Playground</a></h4>
+<p>A simple interactive example of the R-Tree. Great for understanding R-Tree principles.
+</p>
+<h4><a href="rtree.html">Area Effect</a></h4>
+<p>Generates a group of 6500 random small non-overlapping rectangles and shows the resultant tree-structure using a &quot;heat-map&quot;.
+</p>
+<h4><a href="numbers.html">Benchmark</a></h4>
+<p>Inserts 10k objects. Performs some searches. Inserts another 10k objects. Performs some searches. Deletes and reinserts some objects. Performs some final searches.
+<a href="rtree_benchmark.png">Screenshot of various benchmark results</a></p>
+<h4><a href="canvas_test_generate.html">Super-Harcore Example</a></h4>
+<p><b>ONLY run on Opera, Chrome, Safari, or Firefox 3.6 nightlies!</b><br/>
+This test generates 20k dense non-overlapping rectangles of various size and color.
+</p>
+</body>
+</html>
121 examples/numbers.html
@@ -0,0 +1,121 @@
+<html>
+<head>
+ <title>R-Tree for Javascript - Just the numbers</title>
+ <script src="../libs/jquery.min.js"></script>
+ <script src="../src/rtree.js"></script>
+ <script>
+ $(document).ready(function() {
+ var rt = new RTree(32);
+ var deleted_set = new Array();
+ var test_name = ['Inserting 10k elements..','Performing 1k window queries..','Inserting 10k more elements..','Performing 1k window queries..','Deleting 1k elements..','Reinserting the deleted 1k elements..','Performing 1k window queries..'];
+ var test_func = new Array(7);
+ test_func[0] = function(i) {
+ var bounds;
+ while(i > 0) {
+ bounds = {x:(Math.random()*100000), y:(Math.random()*100000), w:(Math.random()*500), h:(Math.random()*500)};
+ rt.insert(bounds, "JUST A TEST OBJECT!_"+i);
+ i--;
+ }
+ };
+ test_func[1] = function(i) {
+ var bounds;
+ i/=10;
+ while(i > 0) {
+ bounds = {x:(Math.random()*95000), y:(Math.random()*95000), w:(Math.random()*1000), h:(Math.random()*1000)};
+ rt.search(bounds);
+ i--;
+ }
+ };
+ test_func[2] = function(i) {
+ var bounds;
+ while(i > 0) {
+ bounds = {x:(Math.random()*100000), y:(Math.random()*100000), w:(Math.random()*500), h:(Math.random()*500)};
+ rt.insert(bounds, "JUST ANOTHER TEST OBJECT!_"+i);
+ i--;
+ }
+ };
+ test_func[3] = function(i) {
+ var bounds;
+ i/=10;
+ while(i > 0) {
+ bounds = {x:(Math.random()*95000), y:(Math.random()*95000), w:(Math.random()*1000), h:(Math.random()*1000)};
+ rt.search(bounds);
+ i--;
+ }
+ };
+ test_func[4] = function(i) {
+ var bounds;
+ var output;
+ i/=10;
+ var original_i = i;
+ while(i > 0) {
+ bounds = {x:(Math.random()*95000), y:(Math.random()*95000), w:(Math.random()*1000), h:(Math.random()*1000)};
+ output = rt.remove(bounds);
+ if(output.length > 0) {
+ i-=output.length;
+ while(i < 0) {
+ t=output.pop();
+ rt.insert(t, t.leaf);
+ i++;
+ }
+ deleted_set = deleted_set.concat(output);
+ }
+ }
+ };
+ test_func[5] = function(i) {
+ var output;
+ i = 0;
+ while(deleted_set.length > 0) {
+ output = deleted_set.pop();
+ rt.insert(output, output.leaf);
+ i++;
+ }
+ };
+ test_func[6] = function(i) {
+ var bounds;
+ i/=10;
+ while(i > 0) {
+ bounds = {x:(Math.random()*95000), y:(Math.random()*95000), w:(Math.random()*1000), h:(Math.random()*1000)};
+ rt.search(bounds);
+ i--;
+ }
+ };
+ var ti = 0;
+ var yield_some = function() {
+ var results_name = "results_"+ti;
+ if(ti % 2 == 0)
+ var test_output = $('<div class="even_row" id="'+results_name+'"><div>'+test_name[ti]+'</div></div>');
+ else
+ var test_output = $('<div class="odd_row" id="'+results_name+'"><div>'+test_name[ti]+'</div></div>');
+ $("#results").append(test_output);
+ var start = (new Date).getTime();
+ test_func[ti](10000);
+ var diff = (new Date).getTime() - start;
+ $('<div>Time To Run: '+diff+'ms</div>').appendTo($("#"+results_name));
+ $('<div>Number of Elements in Tree: '+rt.count({x:-1, y:-1, w:100600, h:100600})+'</div>').appendTo($("#"+results_name));
+ ti+=1;
+ if(ti < 7) {
+ setTimeout(yield_some, 1000);
+ return;
+ }
+ delete rt;
+ }
+ yield_some();
+ });
+ </script>
+ <style>
+ .even_row { padding:10px;
+ background-color:#fefefe;
+ }
+ .odd_row { padding:10px;
+ background-color:#efefff;
+ }
+ </style>
+</head>
+<body>
+<h1>Raw freaking R-Tree numbers!</h1>
+<p>Note the faster searches after re-insertion of the same elements that were deleted.</p>
+<div id="results">
+</div>
+</body>
+</html>
146 examples/rtree.html
@@ -0,0 +1,146 @@
+<html>
+
+<script type='text/javascript' src='../libs/firebug-lite-compressed.js'></script>
+<script type='text/javascript' src="../libs/excanvas.compiled.js"></script>
+<script type='text/javascript' src="../src/rtree.js"></script>
+<script type='text/javascript'>
+var temp_tree = new RTree();
+function getMousePos(e)
+{
+ var posx = 0;
+ var posy = 0;
+ if (!e) var e = window.event;
+ if (e.pageX || e.pageY) {
+ posx = e.pageX;
+ posy = e.pageY;
+ }
+ else if (e.clientX || e.clientY) {
+ posx = e.clientX + document.body.scrollLeft
+ + document.documentElement.scrollLeft;
+ posy = e.clientY + document.body.scrollTop
+ + document.documentElement.scrollTop;
+ }
+ // posx and posy contain the mouse position relative to the document
+ // Do something with this information
+ var tcanvas = document.getElementById("tree_canvas");
+ posx = posx - tcanvas.offsetLeft; posy = posy - tcanvas.offsetTop;
+ return([posx*10,posy*10]);
+
+}
+function dodown(e)
+{
+
+}
+
+function doclick(e)
+{
+ pos = getMousePos(e);
+ console.time("Deleting a portion of "+yy+" objects within a 100x100 pixel window");
+ var t = temp_tree.remove({x:pos[0]-500,y:pos[1]-500,w:1000,h:1000});
+ console.timeEnd("Deleting a portion of "+yy+" objects within a 100x100 pixel window");
+ console.log("Deleted "+t.length+" objects!");
+ draw();
+}
+
+var redraw_timer = null;
+var yy=0;
+var yy_target=6500;
+
+/**
+ * A doload.
+ * @constructor
+ */
+doload = function()
+{
+ var tcanvas = document.getElementById("tree_canvas");
+ if(tcanvas.addEventListener)
+ tcanvas.addEventListener("click", doclick, false);
+
+ console.time("Adding "+yy_target+" non-overlapping objects to the R-Tree");
+ build();
+}
+var tries = 0;
+
+build = function(){
+ tries = 0;
+ while(yy<yy_target)
+ {
+ build_step();
+ if(tries >= 500)
+ {
+ // console.time("Drawing 20k objects from the R-Tree");
+ // draw();
+ // console.timeEnd("Drawing 20k objects from the R-Tree");
+ setTimeout(build, 1); // For no script timeout
+ return;
+ }
+ }
+ console.timeEnd("Adding "+yy_target+" non-overlapping objects to the R-Tree");
+ draw();
+}
+
+build_step = function()
+{
+ var bounds = {x:(Math.random()*12000+200), y:(Math.random()*8800+100), w:(Math.random()*50+1), h:(Math.random()*50+1)};
+ if(/*temp_tree.count(bounds)*/0 == 0)
+ {
+ temp_tree.insert(bounds, {id:"test_object_"+yy, color:"white"});
+ yy+=1;
+ }
+ tries += 1;
+}
+
+function draw_nodes(context)
+{
+ var hit_stack = []; // Contains the local elements (at current tree depth) that overlap
+ var drawn = 0;
+ var leaf_stack = [];
+ hit_stack.push(temp_tree.get_tree().nodes);
+ context.strokeRect(temp_tree.get_tree().x/10, temp_tree.get_tree().y/10, temp_tree.get_tree().w/10, temp_tree.get_tree().h/10);
+ context.strokeStyle = "rgba(155,50,50,0.1)";
+ context.fillStyle = "rgba(225,155,155,0.01)";
+ do
+ {
+ var nodes = hit_stack.pop();
+
+ for(var i = nodes.length-1; i >= 0; i--)
+ {
+ var ltree = nodes[i];
+ if("nodes" in ltree) // Not a Leaf
+ {
+ context.strokeRect(ltree.x/10, ltree.y/10, ltree.w/10, ltree.h/10);
+ context.fillRect(ltree.x/10, ltree.y/10, ltree.w/10, ltree.h/10);
+ hit_stack.push(ltree.nodes);
+ }else
+ leaf_stack.push(ltree);
+ }
+ }while(hit_stack.length > 0);
+ context.fillStyle = "rgba(20,20,20,0.8)";
+ do
+ {
+ var leaf = leaf_stack.pop();
+ drawn++;
+ context.fillRect(leaf.x/10, leaf.y/10, leaf.w/10, leaf.h/10);
+ }while(leaf_stack.length > 0);
+ return drawn;
+}
+
+function draw(){
+ var tcanvas = document.getElementById("tree_canvas");
+ var ctx = tcanvas.getContext("2d");
+ ctx.clearRect(0, 0, tcanvas.width, tcanvas.height)
+ ctx.fillStyle = "rgb(0,0,0)";
+
+
+ ctx.strokeStyle = "rgb(0,150,0)";
+ var drawn = draw_nodes(ctx);
+
+ ctx.clearRect(0, 0, 160, 15);
+ if("fillText" in ctx) ctx.fillText("Current # of Elements: " + drawn, 10, 10);
+}
+</script>
+<body onload="doload()">
+ This test generates 6500 small and non-overlapping rectangles. Each left mouse click searches a 100x100 pixel window deleting any elements it funds. The time taken is reported in the firebug-lite console.<br/>
+ <canvas id="tree_canvas" width="1250" height="900" style="border:1px solid #000;">
+ </canvas>
+</body>
469 examples/simple_test.html
@@ -0,0 +1,469 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>More Canvas Fun</title>
+<script type='text/javascript' src="../src/rtree.js"></script>
+</head>
+<body>
+ The black outline is the tree's root node. Each pink outline is a sub-node.<br/>
+ Click on a rectangle to delete it and see the result in the R-Tree. <br/>
+ Click on any blank area to create a new rectangle and add it to the R-Tree.<br/>
+ <canvas id="game" width="1000px" height="500px" style="border:1px solid blue;">
+ </canvas>
+<script>
+var new_funcs = function(e){canvwrap.getTree().remove({x:e.x,y:e.y,w:1,h:1});};
+var dirty_frame = false;
+var border_width = 4.0; // default?
+
+switch(parseInt((Math.random()*7.99)))
+{
+ case 0:
+ col_name="Primarily Mellow";
+ cols =['#B01E1E','#174385','#15592F','#FCDC0A','#000000'];border_width = 3.0;
+ break;
+ case 1:
+ col_name="A Sad Memory";
+ cols =['#1C465C','#597B6B','#D5C263','#E8DF97','#2D88A5'];border_width = 5.0;
+ break;
+ case 2:
+ col_name="Patriot Games";
+ cols =['#ffffff','#353842','#5E6669','#BED1AE','#5E6669','#DEEFBB','#5E6669','#BED1AE','#5E6669','#DEEFBB','#C7493B'];border_width = 3.0;
+ break;
+ case 3:
+ col_name="Weakerthans";
+ cols =['#ffffff', '#292929','#292929','#E2E2E2','#E2E2E2','#E2E2E2','#1A4685','#E80101','#FFBF15'];border_width = 1.0;
+ break;
+ case 4:
+ col_name="Stoicism";
+ cols =['#303030','#303030','#303030','#303030','#303030','#303030','#303030','#303030','#F5EDDF','#F5EDDF','#F5EDDF','#F5EDDF','#F5EDDF','#EDE592','#EDE592','#BFC7C7','#BFC7C7','#D84818']; border_width = 1.0;
+ break;
+ case 5:
+ col_name="Fish bowl";
+ cols =['#BDCCDF', '#BDD1DE', '#BDDEDE', '#BDDED7', '#FC8B4A'];border_width = 1.0;
+ break;
+ case 6:
+ col_name="Bull in a China Shop";
+ cols =['#FDECB1','#F7E5B5','#3A271F','#C51616','#EDDBB4'];border_width = 3.0;
+ break;
+ case 7:
+ col_name="A Funk Odyssey";
+ cols =['#7A6C5D','#7A6C5D','#7A6C5D','#7A6C5D','#7A6C5D','#7A6C5D','#F0B603','#F0B603','#F0B603','#F0B603','#F0B603','#98AAAC','#98AAAC','#98AAAC','#A3033D','#A3033D','#E9FABE'];border_width = 3.0;
+ break;
+}
+</script>
+<script src="20k_tree.js"></script>
+ <script type="text/javascript">
+
+ /* I want to be able to do: (one day)
+ * cQuery:
+ * addObject(object_interface)
+ *
+ *
+ * cQuery(Rectangle | CanvasObject) [decorated array]
+ *
+ * object interface:
+ * attachEvent(event_name,function())
+ * removeEvent(event_name)
+ * doAreaEvent(event_name)
+ *
+ */
+/********************************************
+ * Full Canvas Library *
+ ********************************************/
+
+var canvwrap=(function(){
+
+ var canv=null,
+ canvctx=null,
+ rt=new RTree(),
+ layers={'main':0},
+ mouse={track:false,x:0,y:0},
+ mouseouts=[],
+ mouseoutids=0;
+
+ var canvasElement=function(x,y,w,h){
+ this.x=x;
+ this.y=y;
+ this.w=w;
+ this.h=h;
+ }
+
+ canvasElement.prototype.attachEvent=function(action,func,layer){
+ canvwrap.attachEvent({x:this.x,y:this.y,w:this.w,h:this.h},action,func,layer);
+ }
+
+ canvasElement.prototype.toString=function(){ return '[CanvasElement]'; };
+
+ function addIndex(bounds,id,action,func,layer, opts)
+ {
+ if(!func) return;
+ if(!layer) layer='main';
+
+ // Get all overlapping objects from the tree
+ var local_objs = rt.search({x:bounds.x,y:bounds.y,w:bounds.w,h:bounds.h});
+ var new_obj = true;
+
+ // Look for an existing object with our same ID
+ while(local_objs.length > 0)
+ {
+ obj = local_objs.pop();
+ if(obj.id == id)
+ {
+ new_obj = false;
+ break;
+ }
+ }
+ // If no object was found, make a new one...
+ if(new_obj) obj = {"func":{}, "action":{}, "id":id};
+
+ // If no events have been attached to the canvas, attach them..
+ if((action=='mouseover' || action=='mouseout' || action=='mousemove') && !mouse.track)
+ {
+ mouse.track=true;
+ canv.addEvent('mousemove',__mouseMove);
+ }
+
+ for (var k in opts)
+ obj[k] = opts[k];
+
+
+ // Update our obj's properties
+ obj.func[action] = func;
+ obj.action[action] = true;
+ obj.layer = layers[layer];
+ if(new_obj) rt.insert(bounds,obj);
+ }
+
+ function treeSearch(x,y,w,h)
+ {
+/* if(window["console"] && console.time)
+ console.time("Searching Tree...");*/
+ var results=rt.search({x:x,y:y,w:w,h:h});
+/* if(window["console"] && console.timeEnd)
+ console.timeEnd("Searching Tree...");*/
+
+ if(results.length==0)
+ return [];
+ results.sort(sortRes);
+ return results;
+ }
+
+ function sortRes(a,b){ return b.layer-a.layer; }
+
+ function __mouseDown(e){
+ var xy=getXY(e);
+ xy.x = canv_off_x+xy.x/zoom;
+ xy.y = canv_off_y+xy.y/zoom;
+ var results=treeSearch(xy.x,xy.y,1,1);
+ var result_count = results.length;
+ if(result_count > 0) {
+ for(var i=0;i<results.length;i++) {
+ if(results[i].action[e.type])
+ results[i].func[e.type].apply(results[i], [{x:xy.x,y:xy.y}]);
+ }
+ } else {
+ var bounds = {x:xy.x, y:xy.y, w:(Math.random()*10), h:(Math.random()*10)};
+ var opts = {color:cols[(Math.random()*100).toFixed()%cols.length]};
+ if(rt.count(bounds) == 0)
+ {
+ canvwrap.attachEvent(bounds,test_damnit,'mousedown', new_funcs, null, opts);
+ test_damnit++;
+ }
+ }
+ dirty_frame = true;
+ }
+
+ function __drawImage(image, dx, dy)
+ {
+ if(arguments[8])
+ {
+ sx=dx;
+ sy=dy;
+ sw=arguments[3];
+ sh=arguments[4];
+ dx=arguments[5];
+ dy=arguments[6];
+ dw=arguments[7];
+ dh=arguments[8];
+ canvctx.drawImage(image,sx,sy,sw,sh,dx,dy,dw,dh);
+ return new canvasElement(dx,dy,dw,dh);
+ }
+
+ if(arguments[3] && arguments[4])
+ {
+ dw=arguments[3];
+ dh=arguments[4];
+ }
+ else
+ {
+ dw=image.width;
+ dh=image.height;
+ }
+ canvctx.drawImage(image,dx,dy,dw,dh);
+ return new canvasElement(dx,dy,dw,dh);
+ }
+
+ function getXY(e)
+ {
+ var posx = 0;
+ var posy = 0;
+ if (!e) var e = window.event;
+ if (e.pageX || e.pageY) {
+ posx = e.pageX;
+ posy = e.pageY;
+ }
+ else if (e.clientX || e.clientY) {
+ posx = e.clientX + document.body.scrollLeft
+ + document.documentElement.scrollLeft;
+ posy = e.clientY + document.body.scrollTop
+ + document.documentElement.scrollTop;
+ }
+ posx=posx-canv.offsetLeft;
+ posy=posy-canv.offsetTop;
+ return {x:posx,y:posy};
+ }
+
+ function __mouseMove(e){
+ var xy=getXY(e);
+ xy.x = canv_off_x+xy.x/zoom;
+ xy.y = canv_off_y+xy.y/zoom;
+
+ var results=treeSearch(xy.x,xy.y,1,1),
+ thrown=null,
+ entryfound=false;
+ var results_copy = results.slice(0, results.length);
+ var l_mouse_ins = []; // Holds new (mousein) elements
+ var l_mouse_moves = []; // holds all repeated elements
+ // mouseouts will hold mouseouts
+ mouse.x=xy.x;
+ mouse.y=xy.y;
+
+ // Seperate the elements from mouseouts + results into three arrays (in, out, move)
+ while(results.length > 0)
+ {
+ var current_results = results.pop();
+
+ var m=0;
+ while(m<mouseouts.length)
+ {
+ if(mouseouts[m].id === current_results) //IF it was found in "results" then it is a move
+ {
+ l_mouse_moves.push(mouseouts.splice(m));
+ m-=1;
+ current_results = null;
+ }
+ m+=1;
+ }
+ if(current_results) //IF it wasn't found in "results" then it is new
+ l_mouse_ins.push(current_results);
+ }
+
+ // now the three arrays contain the elements they should..
+ while(mouseouts.length>0)
+ {
+ var thrown=mouseouts.pop();
+ if(thrown.action['mouseout'])
+ thrown.func['mouseout']({x:xy.x,y:xy.y});
+ }
+ while(l_mouse_ins.length>0)
+ {
+ var thrown=l_mouse_ins.pop();
+ if(thrown.action['mouseover'])
+ thrown.func['mouseover']({x:xy.x,y:xy.y});
+ }
+ while(l_mouse_moves.length>0)
+ {
+ var thrown=l_mouse_moves.pop();
+ if(thrown.action['mousemove'])
+ thrown.func['mousemove']({x:xy.x,y:xy.y});
+ }
+ mouseouts = results_copy;
+ }
+
+ function init(canvid, width, height)
+ {
+ canv=document.getElementById(canvid);
+ canv.addEvent=function(type,func){
+ if(canv.addEventListener)
+ canv.addEventListener(type,func,false);
+ else if(canv.attachEvent)
+ canv.attachEvent(type,func);
+ };
+
+ canv.addEvent('mousedown',__mouseDown);
+ // canv.addEvent('mouseup',__mouseDown);
+ // canv.addEvent('click',__mouseDown);
+ // canv.addEvent('dblclick',__mouseDown);
+
+ if(canv.getContext)
+ canvctx=canv.getContext('2d');
+ }
+
+ return {
+ init: init,
+ attachEvent: addIndex,
+ drawImage: __drawImage,
+ getTree: function(){return(rt);}
+ }
+
+})();
+
+canvwrap.init("game", 650, 475);
+
+var ctx=document.getElementById('game');
+ctx = ctx.getContext('2d');
+
+function test(dir){
+ //alert(dir);
+ var dirarr=['top_left', 'top', 'top_right', 'right', 'bottom_right', 'bottom', 'bottom_left', 'left'];
+ document.getElementById('status').innerHTML=dirarr[dir] + " was clicked!";
+}
+
+var canv_off_x = 0;
+var canv_del_x = 0;
+var canv_off_y = 0;
+var canv_del_y = 0;
+var zoom = 1;
+var zdel = 0.99; // speed of zoom
+zdel = 1.0;
+
+var fps_timer = false;
+var draw_tree = true;
+
+function draw_nodes(context)
+{
+ var hit_stack = []; // Contains the local elements (at current tree depth) that overlap
+
+ var to_draw = 0;
+ var hit_stack = []; // Contains the elements that overlap
+ var T = canvwrap.getTree().get_tree();
+ var rect = {x:canv_off_x, y:canv_off_y, w:document.getElementById("game").width*zoom, h:document.getElementById("game").height*zoom };
+ context.strokeStyle = "rgb(0,0,0)";
+ context.lineWidth = 0.5;
+ context.strokeRect((T.x-canv_off_x)*zoom, (T.y-canv_off_y)*zoom, T.w*zoom, T.h*zoom);
+ context.strokeStyle = "rgb(0,0,0)";
+ context.lineWidth = border_width*zoom;
+
+ if(!RTree.Rectangle.overlap_rectangle(rect, T))
+ return(0);
+
+ hit_stack.push(T.nodes);
+
+/* if(window["console"] && console.time && fps_timer)
+ console.time("Draw Frame...");
+*/
+ do
+ {
+ var nodes = hit_stack.pop();
+ for(var i = nodes.length-1; i >= 0; i--)
+ {
+ var el = nodes[i];
+ if(RTree.Rectangle.overlap_rectangle(rect, el))
+ {
+ if(el.nodes) // Not a Leaf
+ {
+ hit_stack.push(el.nodes);
+ if(draw_tree)
+ {
+ context.strokeStyle = "rgb(200,100,100)";
+ context.lineWidth = 0.33;
+ context.strokeRect((el.x-canv_off_x)*zoom, (el.y-canv_off_y)*zoom, el.w*zoom, el.h*zoom);
+
+ //return to old style
+ context.strokeStyle = "rgb(0,0,0)";
+ context.lineWidth = border_width*zoom;
+ to_draw++;
+ }
+ }
+ else // A Leaf !!
+ {
+// if(!draw_tree)
+// {
+ to_draw++;
+ context.strokeRect((el.x-canv_off_x)*zoom, (el.y-canv_off_y)*zoom, el.w*zoom, el.h*zoom);
+ context.fillStyle=el.leaf.color;
+ context.fillRect((el.x-canv_off_x)*zoom, (el.y-canv_off_y)*zoom, el.w*zoom, el.h*zoom);
+// }
+ }
+ }
+ }
+ }while(hit_stack.length > 0);
+
+/* if(window["console"] && console.timeEnd && fps_timer && !(fps_timer = false))
+ console.timeEnd("Draw Frame...");
+*/
+ return(to_draw);
+}
+
+
+
+
+function draw(){
+ if(dirty_frame) {
+ dirty_frame = false;
+ var tcanvas = document.getElementById("game");
+ var ctx = tcanvas.getContext("2d");
+ ctx.clearRect(0, 0, tcanvas.width, tcanvas.height)
+ //ctx.fillStyle = "rgb(0,0,0)";
+
+ //ctx.strokeStyle = "rgb(0,150,0)";
+ ctx.fillStyle="rgb(200,200,200)";
+
+ var drawn = draw_nodes(ctx);
+
+ ctx.clearRect(0, 0, 160, 40);
+ ctx.fillStyle="rgb(0,0,0)";
+ ctx.fillText("# of Elements on Screen: " + drawn, 10, 10);
+ ctx.fillText("# of Elements in Tree: " + test_damnit, 10, 20);
+ ctx.fillText("-= " + col_name +" =-", 10, 30);
+ }
+}
+
+q=0;
+var test_damnit = 0;
+
+
+// LOAD PRE-BUILT TREE
+
+//console.log(canvwrap.getTree().set_tree(OBJ_TREE_20K)); test_damnit = 20000; canv_del_x = canv_del_y = -2;
+
+if( canvwrap.getTree().get_tree().nodes.length == 0 )
+{
+ var do_some_then_yield = function(){
+ var loops = 0; var yy= (Math.random()*10);
+ var bounds = {x:(Math.random()*969), y:yy*30+50, w:(Math.random()*25+5), h:(Math.random()*25+5)};
+ var opts = {color:cols[(Math.random()*100).toFixed()%cols.length]};
+ while(test_damnit < 10)
+ {
+ if(canvwrap.getTree().count({x:bounds.x, y:bounds.y, w:bounds.w, h:bounds.h}) == 0)
+ {
+ canvwrap.attachEvent(bounds,test_damnit,'mousedown', new_funcs, null, opts);
+ test_damnit++;
+ yy= (Math.random()*20);
+ bounds = {x:(Math.random()*969), y:yy*30+50, w:(Math.random()*25+5), h:(Math.random()*25+5)};
+ var opts = {color:cols[(Math.random()*123).toFixed()%cols.length]};
+ }
+ else
+ {
+ yy= (Math.random()*20);
+ bounds.x=(Math.random()*969); bounds.y=yy*30+50; bounds.w=(Math.random()*25+5); bounds.h=(Math.random()*25+5);
+ }
+ loops++;
+ if(loops >= 1)
+ {
+ dirty_frame = true;
+ setTimeout(do_some_then_yield, 250);
+ return;
+ }
+ }
+ //clearInterval(draw_timer);
+ //canv_del_x = canv_del_y = -2;
+ };
+ do_some_then_yield();
+}
+//canv_del_x = canv_del_y = -2;
+draw_timer = setInterval(function(){ if(!dirty_frame) return; if(zoom > 2 || zoom < 0.4) zdel = 1.0 / zdel; if(canv_off_x > 220 || canv_off_x < 10) canv_del_x*=-1; if(canv_off_y > 160 || canv_off_y < 10) canv_del_y*=-1; zoom *= zdel; canv_off_x += (canv_del_x/zoom); canv_off_y += (canv_del_y/zoom); draw();}, 33); // Yield to browser temporarily to suppress "script is taking too damn long!" alert
+
+</script>
+</body>
+</html>
197 examples/test.html
@@ -0,0 +1,197 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Slowdowns.. bleh</title>
+</head>
+<body>
+ <canvas style="margin-top: 100px;" id="game" width="650px" height="475px">
+ </canvas>
+ <script type="text/javascript" src="../src/rtree.js"></script>
+ <script type="text/javascript">
+CanvasWrapper=function(canvasid,width,height){
+
+ var canv=null,
+ canvctx=null,
+ rt=new RTree(),
+ layers={'main':0},
+ mouse={track:false,x:0,y:0},
+ old_results = []; // Holds old mousing elements (for _mouseMove)
+
+ canv=document.getElementById(canvasid);
+ canv["x"] = 0; canv["y"] = 0;
+ canv["w"] = canv.width; canv["h"] = canv.height;
+
+ canv.addEvent=function(type,func){
+ if(window.addEventListener)
+ window.addEventListener(type,func,false);
+ else if(window.attachEvent)
+ window.attachEvent(type,func);
+ };
+
+ canv.addEvent('mousedown',__mouseDown);
+ canv.addEvent('mouseup',__mouseDown);
+ canv.addEvent('click',__mouseDown);
+ canv.addEvent('dblclick',__mouseDown);
+
+ if(canv.getContext)
+ canvctx=canv.getContext('2d');
+
+ // This needs to be created into a public function for AddObject, not events
+ this.insertIndex=function(bounds,id,action,func,layer)
+ {
+ if(!func) return;
+ if(!layer) layer='main';
+
+ // Get all overlapping objects from the tree
+ var local_objs = treeSearch(bounds);
+ var new_obj = true;
+
+ // Look for an existing object with our same ID
+ while(local_objs.length > 0)
+ {
+ obj = local_objs.pop();
+ if(obj.id == id)
+ {
+ new_obj = false;
+ break;
+ }
+ }
+ // If no object was found, make a new one...
+ if(new_obj) obj = {"func":{}, "action":{}, "bounds":{}, "id":id};
+
+ // If no events have been attached to the canvas, attach them..
+ if((action=='mouseover' || action=='mouseout' || action=='mousemove') && !mouse.track)
+ {
+ mouse.track=true;
+ canv.addEvent('mousemove',__mouseMove);
+ }
+
+ // Update our obj's properties
+ obj.func[action] = func;
+ obj.bounds[action] = bounds;
+ obj.action[action] = true;
+ obj.layer = layers[layer];
+ if(new_obj) rt.insert(bounds,obj);
+ }
+
+ function treeSearch(rect)
+ {
+ var results=rt.search(rect);
+ if(results.length==0)
+ return [];
+ results.sort(sortRes);
+ return results;
+ }
+
+ function sortRes(a,b){ return b.layer-a.layer; }
+
+ function __mouseDown(e){
+ var xy=getXY(e),
+ results=treeSearch(xy);
+ for(var i=0;i<results.length;i++)
+ if(results[i].action[e.type])
+ results[i].func[e.type].apply(results[i], [xy.x,xy.y]);
+ }
+
+ // SET_NOT returns an array containing all elements in A that are NOT IN B
+ function SET_NOT(A, B) {
+ var a_len = A.length, b_len = B.length;
+ var ret_array = [], a_found_in_b = false;
+ for(var ai = 0; ai < a_len; ai++) {
+ for(var bi = 0; bi < b_len; bi++) {
+ if(A[ai] === B[bi])
+ a_found_in_b = true;
+ }
+ if(!a_found_in_b)
+ ret_array.push(A[ai]);
+ a_found_in_b = false;
+ }
+ return(ret_array);
+ }
+
+ function __mouseMove(e) {
+ var xy=getXY(e);
+
+ // everything in l_results is a mouseMOVE
+ var l_results=RTree.Rectangle.overlap_rectangle(xy, canv)?treeSearch(xy):[];
+
+ // whatever is in l_results but NOT in old_results is a mouseOVER
+ var l_mouse_ins = SET_NOT(l_results, old_results);
+
+ // whatever is in old_results but NOT in l_results is a mouseOUT
+ var l_mouse_outs = SET_NOT(old_results, l_results);
+
+ while(l_mouse_outs.length>0) {
+ var thrown=l_mouse_outs.pop();
+ if(thrown.action['mouseout'])
+ thrown.func['mouseout'].apply(thrown, [thrown.bounds['mouseout'].x,thrown.bounds['mouseout'].y]);
+ }
+ while(l_mouse_ins.length>0) {
+ var thrown=l_mouse_ins.pop();
+ if(thrown.action['mouseover'])
+ thrown.func['mouseover'].apply(thrown, [thrown.bounds['mouseover'].x,thrown.bounds['mouseover'].y]);
+ }
+ old_results = l_results.splice(0, l_results.length);
+ while(l_results.length>0) {
+ var thrown=results.pop();
+ if(thrown.action['mousemove'])
+ thrown.func['mousemove'].apply(thrown, [thrown.bounds['mousemove'].x,thrown.bounds['mousemove'].y]);
+ }
+ }
+
+ function getXY(e) {
+ var posx = 0;
+ var posy = 0;
+ if (!e) var e = window.event;
+ if (e.pageX || e.pageY) {
+ posx = e.pageX;
+ posy = e.pageY;
+ }
+ else if (e.clientX || e.clientY) {
+ posx = e.clientX + document.body.scrollLeft
+ + document.documentElement.scrollLeft;
+ posy = e.clientY + document.body.scrollTop
+ + document.documentElement.scrollTop;
+ }
+ posx=posx-canv.offsetLeft;
+ posy=posy-canv.offsetTop;
+ return {x:posx,y:posy, w:1, h:1};
+ }
+}
+
+
+var ctx=document.getElementById('game').getContext('2d');
+
+var test=new CanvasWrapper("game",650,475);
+
+var cols=['red','blue','green','orange','yellow','silver'];
+var x=0,y=0,w=10,h=10;
+for(var i=0;i<8000;i++)
+{
+ x=rnd(5,600);
+ y=rnd(5,450);
+ test.insertIndex({x:x,y:y,w:w,h:h},"box"+i,'mouseover',highlight);
+ test.insertIndex({x:x,y:y,w:w,h:h},"box"+i,'mouseout',function(b,c){setTimeout(function(){ctx.fillStyle="grey";ctx.fillRect(b,c,w,h);},500)});
+ ctx.fillStyle="grey";
+ ctx.fillRect(x,y,w,h);
+}
+var m=0;
+function highlight(x,y)
+{
+ ctx.fillStyle=cols[m++>4?m=0:m];
+ ctx.fillRect(x,y,w,h);
+}
+/*
+w=100;
+h=100;
+
+ctx.fillStyle="white";
+ctx.fillRect(100,100,100,100);
+test.insertIndex({x:100,y:100,w:100,h:100},'id','mouseout',function(){setTimeout(function(){ctx.fillStyle="white";ctx.fillRect(100,100,100,100);},60)});
+test.insertIndex({x:100,y:100,w:100,h:100},'id','mouseover',function(){highlight(100,100);});
+*/
+
+function rnd(min,max){ return Math.floor(Math.random()*max+min); }
+ </script>
+</body>
+</html>
224 jsspec/JSSpec.css
@@ -0,0 +1,224 @@
+@CHARSET "UTF-8";
+
+/* --------------------
+ * @Layout
+ */
+
+html {
+ overflow: hidden;
+}
+
+body, #jsspec_container {
+ overflow: hidden;
+ padding: 0;
+ margin: 0;
+ width: 100%;
+ height: 100%;
+ background-color: white;
+}
+
+#title {
+ padding: 0;
+ margin: 0;
+ position: absolute;
+ top: 0px;
+ left: 0px;
+ width: 100%;
+ height: 40px;
+ overflow: hidden;
+}
+
+#list {
+ padding: 0;
+ margin: 0;
+ position: absolute;
+ top: 40px;
+ left: 0px;
+ bottom: 0px;
+ overflow: auto;
+ width: 250px;
+ _height:expression(document.body.clientHeight-40);
+}
+
+#log {
+ padding: 0;
+ margin: 0;
+ position: absolute;
+ top: 40px;
+ left: 250px;
+ right: 0px;
+ bottom: 0px;
+ overflow: auto;
+ _height:expression(document.body.clientHeight-40);
+ _width:expression(document.body.clientWidth-250);
+}
+
+
+
+/* --------------------
+ * @Decorations and colors
+ */
+* {
+ padding: 0;
+ margin: 0;
+ font-family: "Lucida Grande", Helvetica, sans-serif;
+}
+
+li {
+ list-style: none;
+}
+
+/* hiding subtitles */
+h2 {
+ display: none;
+}
+
+/* title section */
+div#title {
+ padding: 0em 0.5em;
+}
+
+div#title h1 {
+ font-size: 1.5em;
+ float: left;
+}
+
+div#title ul li {
+ float: left;
+ padding: 0.5em 0em 0.5em 0.75em;
+}
+
+div#title p {
+ float:right;
+ margin-right:1em;
+ font-size: 0.75em;
+}
+
+/* spec container */
+ul.specs {
+ margin: 0.5em;
+}
+ul.specs li {
+ margin-bottom: 0.1em;
+}
+
+/* spec title */
+ul.specs li h3 {
+ font-weight: bold;
+ font-size: 0.75em;
+ padding: 0.2em 1em;
+ cursor: pointer;
+ _cursor: hand;
+}
+
+/* example container */
+ul.examples li {
+ border-style: solid;
+ border-width: 0px 0px 1px 5px;
+ margin: 0.2em 0em 0.2em 1em;
+}
+
+/* example title */
+ul.examples li h4 {
+ font-weight: normal;
+ font-size: 0.75em;
+ margin-left: 1em;
+}
+
+pre.examples-code {
+ margin: 0.5em 2em;
+ padding: 0.5em;
+ background: white;
+ border: solid 1px #CCC;
+}
+
+/* example explaination */
+ul.examples li div {
+ padding: 1em 2em;
+ font-size: 0.75em;
+}
+
+/* styles for ongoing, success, failure, error */
+div.success, div.success a {
+ color: #FFFFFF;
+ background-color: #65C400;
+}
+
+ul.specs li.success h3, ul.specs li.success h3 a {
+ color: #FFFFFF;
+ background-color: #65C400;
+}
+
+ul.examples li.success, ul.examples li.success a {
+ color: #3D7700;
+ background-color: #DBFFB4;
+ border-color: #65C400;
+}
+
+div.exception, div.exception a {
+ color: #FFFFFF;
+ background-color: #C20000;
+}
+
+ul.specs li.exception h3, ul.specs li.exception h3 a {
+ color: #FFFFFF;
+ background-color: #C20000;
+}
+
+ul.examples li.exception, ul.examples li.exception a {
+ color: #C20000;
+ background-color: #FFFBD3;
+ border-color: #C20000;
+}
+
+div.ongoing, div.ongoing a {
+ color: #000000;
+ background-color: #FFFF80;
+}
+
+ul.specs li.ongoing h3, ul.specs li.ongoing h3 a {
+ color: #000000;
+ background-color: #FFFF80;
+}
+
+ul.examples li.ongoing, ul.examples li.ongoing a {
+ color: #000000;
+ background-color: #FFFF80;
+ border-color: #DDDD00;
+}
+
+
+
+/* --------------------
+ * values
+ */
+.number_value, .string_value, .regexp_value, .boolean_value, .dom_value {
+ font-family: monospace;
+ color: blue;
+}
+.object_value, .array_value {
+ line-height: 2em;
+ padding: 0.1em 0.2em;
+ margin: 0.1em 0;
+}
+.date_value {
+ font-family: monospace;
+ color: olive;
+}
+.undefined_value, .null_value {
+ font-style: italic;
+ color: blue;
+}
+.dom_attr_name {
+}
+.dom_attr_value {
+ color: red;
+}
+.dom_path {
+ font-size: 0.75em;
+ color: gray;
+}
+strong {
+ font-weight: normal;
+ background-color: #FFC6C6;
+}
1,548 jsspec/JSSpec.js
@@ -0,0 +1,1548 @@
+/**
+ * JSSpec
+ *
+ * Copyright 2007 Alan Kang
+ * - mailto:jania902@gmail.com
+ * - http://jania.pe.kr
+ *
+ * http://jania.pe.kr/aw/moin.cgi/JSSpec
+ *
+ * Dependencies:
+ * - diff_match_patch.js ( http://code.google.com/p/google-diff-match-patch )
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+/**
+ * Namespace
+ */
+
+var JSSpec = {
+ specs: [],
+
+ EMPTY_FUNCTION: function() {},
+
+ Browser: {
+ // By Rendering Engines
+ Trident: navigator.appName === "Microsoft Internet Explorer",
+ Webkit: navigator.userAgent.indexOf('AppleWebKit/') > -1,
+ Gecko: navigator.userAgent.indexOf('Gecko') > -1 && navigator.userAgent.indexOf('KHTML') === -1,
+ KHTML: navigator.userAgent.indexOf('KHTML') !== -1,
+ Presto: navigator.appName === "Opera",
+
+ // By Platforms
+ Mac: navigator.userAgent.indexOf("Macintosh") !== -1,
+ Ubuntu: navigator.userAgent.indexOf('Ubuntu') !== -1,
+ Win: navigator.userAgent.indexOf('Windows') !== -1,
+
+ // By Browsers
+ IE: navigator.appName === "Microsoft Internet Explorer",
+ IE6: navigator.userAgent.indexOf('MSIE 6') !== -1,
+ IE7: navigator.userAgent.indexOf('MSIE 7') !== -1,
+ IE8: navigator.userAgent.indexOf('MSIE 8') !== -1,
+
+ FF: navigator.userAgent.indexOf('Firefox') !== -1,
+ FF2: navigator.userAgent.indexOf('Firefox/2') !== -1,
+ FF3: navigator.userAgent.indexOf('Firefox/3') !== -1,
+ Safari: navigator.userAgent.indexOf('Safari') !== -1
+ }
+};
+
+
+
+/**
+ * Executor
+ */
+JSSpec.Executor = function(target, onSuccess, onException) {
+ this.target = target;
+ this.onSuccess = typeof onSuccess == 'function' ? onSuccess : JSSpec.EMPTY_FUNCTION;
+ this.onException = typeof onException == 'function' ? onException : JSSpec.EMPTY_FUNCTION;
+
+ if(JSSpec.Browser.Trident) {
+ // Exception handler for Trident. It helps to collect exact line number where exception occured.
+ window.onerror = function(message, fileName, lineNumber) {
+ var self = window._curExecutor;
+ var ex = {message:message, fileName:fileName, lineNumber:lineNumber};
+
+ if(JSSpec._secondPass) {
+ ex = self.mergeExceptions(JSSpec._assertionFailure, ex);
+ delete JSSpec._secondPass;
+ delete JSSpec._assertionFailure;
+
+ ex.type = "failure";
+ self.onException(self, ex);
+ } else if(JSSpec._assertionFailure) {
+ JSSpec._secondPass = true;
+ self.run();
+ } else {
+ self.onException(self, ex);
+ }
+
+ return true;
+ };
+ }
+};
+JSSpec.Executor.prototype.mergeExceptions = function(assertionFailure, normalException) {
+ var merged = {
+ message:assertionFailure.message,
+ fileName:normalException.fileName,
+ lineNumber:normalException.lineNumber
+ };
+
+ return merged;
+};
+
+JSSpec.Executor.prototype.run = function() {
+ var self = this;
+ var target = this.target;
+ var onSuccess = this.onSuccess;
+ var onException = this.onException;
+
+ window.setTimeout(
+ function() {
+ var result;
+ if(JSSpec.Browser.Trident) {
+ window._curExecutor = self;
+
+ result = self.target();
+ self.onSuccess(self, result);
+ } else {
+ try {
+ result = self.target();
+ self.onSuccess(self, result);
+ } catch(ex) {
+ if(JSSpec.Browser.Webkit) ex = {message:ex.message, fileName:ex.sourceURL, lineNumber:ex.line};
+
+ if(JSSpec._secondPass) {
+ ex = self.mergeExceptions(JSSpec._assertionFailure, ex);
+ delete JSSpec._secondPass;
+ delete JSSpec._assertionFailure;
+
+ ex.type = "failure";
+ self.onException(self, ex);
+ } else if(JSSpec._assertionFailure) {
+ JSSpec._secondPass = true;
+ self.run();
+ } else {
+ self.onException(self, ex);
+ }
+ }
+ }
+ },
+ 0
+ );
+};
+
+
+
+/**
+ * CompositeExecutor composites one or more executors and execute them sequencially.
+ */
+JSSpec.CompositeExecutor = function(onSuccess, onException, continueOnException) {
+ this.queue = [];
+ this.onSuccess = typeof onSuccess == 'function' ? onSuccess : JSSpec.EMPTY_FUNCTION;
+ this.onException = typeof onException == 'function' ? onException : JSSpec.EMPTY_FUNCTION;
+ this.continueOnException = !!continueOnException;
+};
+
+JSSpec.CompositeExecutor.prototype.addFunction = function(func) {
+ this.addExecutor(new JSSpec.Executor(func));
+};
+
+JSSpec.CompositeExecutor.prototype.addExecutor = function(executor) {
+ var last = this.queue.length == 0 ? null : this.queue[this.queue.length - 1];
+ if(last) {
+ last.next = executor;
+ }
+
+ executor.parent = this;
+ executor.onSuccessBackup = executor.onSuccess;
+ executor.onSuccess = function(result) {
+ this.onSuccessBackup(result);
+ if(this.next) {
+ this.next.run();
+ } else {
+ this.parent.onSuccess();
+ }
+ };
+ executor.onExceptionBackup = executor.onException;
+ executor.onException = function(executor, ex) {
+ this.onExceptionBackup(executor, ex);
+
+ if(this.parent.continueOnException) {
+ if(this.next) {
+ this.next.run();
+ } else {
+ this.parent.onSuccess();
+ }
+ } else {
+ this.parent.onException(executor, ex);
+ }
+ };
+
+ this.queue.push(executor);
+};
+
+JSSpec.CompositeExecutor.prototype.run = function() {
+ if(this.queue.length > 0) {
+ this.queue[0].run();
+ }
+};
+
+/**
+ * Spec is a set of Examples in a specific context
+ */
+JSSpec.Spec = function(context, entries) {
+ this.id = JSSpec.Spec.id++;
+ this.context = context;
+ this.url = location.href;
+
+ this.filterEntriesByEmbeddedExpressions(entries);
+ this.extractOutSpecialEntries(entries);
+ this.examples = this.makeExamplesFromEntries(entries);
+ this.examplesMap = this.makeMapFromExamples(this.examples);
+};
+
+JSSpec.Spec.id = 0;
+JSSpec.Spec.prototype.getExamples = function() {
+ return this.examples;
+};
+
+JSSpec.Spec.prototype.hasException = function() {
+ return this.getTotalFailures() > 0 || this.getTotalErrors() > 0;
+};
+
+JSSpec.Spec.prototype.getTotalFailures = function() {
+ var examples = this.examples;
+ var failures = 0;
+ for(var i = 0; i < examples.length; i++) {
+ if(examples[i].isFailure()) failures++;
+ }
+ return failures;
+};
+
+JSSpec.Spec.prototype.getTotalErrors = function() {
+ var examples = this.examples;
+ var errors = 0;
+ for(var i = 0; i < examples.length; i++) {
+ if(examples[i].isError()) errors++;
+ }
+ return errors;
+};
+
+JSSpec.Spec.prototype.filterEntriesByEmbeddedExpressions = function(entries) {
+ var isTrue;
+ for(name in entries) if(entries.hasOwnProperty(name)) {
+ var m = name.match(/\[\[(.+)\]\]/);
+ if(m && m[1]) {
+ eval("isTrue = (" + m[1] + ")");
+ if(!isTrue) delete entries[name];
+ }
+ }
+};
+
+JSSpec.Spec.prototype.extractOutSpecialEntries = function(entries) {
+ this.beforeEach = JSSpec.EMPTY_FUNCTION;
+ this.beforeAll = JSSpec.EMPTY_FUNCTION;
+ this.afterEach = JSSpec.EMPTY_FUNCTION;
+ this.afterAll = JSSpec.EMPTY_FUNCTION;
+
+ for(name in entries) if(entries.hasOwnProperty(name)) {
+ if(name == 'before' || name == 'before each' || name == 'before_each') {
+ this.beforeEach = entries[name];
+ } else if(name == 'before all' || name == 'before_all') {
+ this.beforeAll = entries[name];
+ } else if(name == 'after' || name == 'after each' || name == 'after_each') {
+ this.afterEach = entries[name];
+ } else if(name == 'after all' || name == 'after_all') {
+ this.afterAll = entries[name];
+ }
+ }
+
+ delete entries['before'];
+ delete entries['before each'];
+ delete entries['before_each'];
+ delete entries['before all'];
+ delete entries['before_all'];
+ delete entries['after'];
+ delete entries['after each'];
+ delete entries['after_each'];
+ delete entries['after all'];
+ delete entries['after_all'];
+};
+
+JSSpec.Spec.prototype.makeExamplesFromEntries = function(entries) {
+ var examples = [];
+ for(name in entries) if(entries.hasOwnProperty(name)) {
+ examples.push(new JSSpec.Example(name, entries[name], this.beforeEach, this.afterEach));
+ }
+ return examples;
+};
+
+JSSpec.Spec.prototype.makeMapFromExamples = function(examples) {
+ var map = {};
+ for(var i = 0; i < examples.length; i++) {
+ var example = examples[i];
+ map[example.id] = examples[i];
+ }
+ return map;
+};
+
+JSSpec.Spec.prototype.getExampleById = function(id) {
+ return this.examplesMap[id];
+};
+
+JSSpec.Spec.prototype.getExecutor = function() {
+ var self = this;
+ var onException = function(executor, ex) {
+ self.exception = ex;
+ };
+
+ var composite = new JSSpec.CompositeExecutor();
+ composite.addFunction(function() {JSSpec.log.onSpecStart(self);});
+ composite.addExecutor(new JSSpec.Executor(this.beforeAll, null, function(exec, ex) {
+ self.exception = ex;
+ JSSpec.log.onSpecEnd(self);
+ }));
+
+ var exampleAndAfter = new JSSpec.CompositeExecutor(null,null,true);
+ for(var i = 0; i < this.examples.length; i++) {
+ exampleAndAfter.addExecutor(this.examples[i].getExecutor());
+ }
+ exampleAndAfter.addExecutor(new JSSpec.Executor(this.afterAll, null, onException));
+ exampleAndAfter.addFunction(function() {JSSpec.log.onSpecEnd(self);});
+ composite.addExecutor(exampleAndAfter);
+
+ return composite;
+};
+
+/**
+ * Example
+ */
+JSSpec.Example = function(name, target, before, after) {
+ this.id = JSSpec.Example.id++;
+ this.name = name;
+ this.target = target;
+ this.before = before;
+ this.after = after;
+};
+
+JSSpec.Example.id = 0;
+JSSpec.Example.prototype.isFailure = function() {
+ return this.exception && this.exception.type == "failure";
+};
+
+JSSpec.Example.prototype.isError = function() {
+ return this.exception && !this.exception.type;
+};
+
+JSSpec.Example.prototype.getExecutor = function() {
+ var self = this;
+ var onException = function(executor, ex) {
+ self.exception = ex;
+ };
+
+ var composite = new JSSpec.CompositeExecutor();
+ composite.addFunction(function() {JSSpec.log.onExampleStart(self);});
+ composite.addExecutor(new JSSpec.Executor(this.before, null, function(exec, ex) {
+ self.exception = ex;
+ JSSpec.log.onExampleEnd(self);
+ }));
+
+ var targetAndAfter = new JSSpec.CompositeExecutor(null,null,true);
+
+ targetAndAfter.addExecutor(new JSSpec.Executor(this.target, null, onException));
+ targetAndAfter.addExecutor(new JSSpec.Executor(this.after, null, onException));
+ targetAndAfter.addFunction(function() {JSSpec.log.onExampleEnd(self);});
+
+ composite.addExecutor(targetAndAfter);
+
+ return composite;
+};
+
+/**
+ * Runner
+ */
+JSSpec.Runner = function(specs, logger) {
+ JSSpec.log = logger;
+
+ this.totalExamples = 0;
+ this.specs = [];
+ this.specsMap = {};
+ this.addAllSpecs(specs);
+};
+
+JSSpec.Runner.prototype.addAllSpecs = function(specs) {
+ for(var i = 0; i < specs.length; i++) {
+ this.addSpec(specs[i]);
+ }
+};
+
+JSSpec.Runner.prototype.addSpec = function(spec) {
+ this.specs.push(spec);
+ this.specsMap[spec.id] = spec;
+ this.totalExamples += spec.getExamples().length;
+};
+
+JSSpec.Runner.prototype.getSpecById = function(id) {
+ return this.specsMap[id];
+};
+
+JSSpec.Runner.prototype.getSpecByContext = function(context) {
+ for(var i = 0; i < this.specs.length; i++) {
+ if(this.specs[i].context == context) return this.specs[i];
+ }
+ return null;
+};
+
+JSSpec.Runner.prototype.getSpecs = function() {
+ return this.specs;
+};
+
+JSSpec.Runner.prototype.hasException = function() {
+ return this.getTotalFailures() > 0 || this.getTotalErrors() > 0;
+};
+
+JSSpec.Runner.prototype.getTotalFailures = function() {
+ var specs = this.specs;
+ var failures = 0;
+ for(var i = 0; i < specs.length; i++) {
+ failures += specs[i].getTotalFailures();
+ }
+ return failures;
+};
+
+JSSpec.Runner.prototype.getTotalErrors = function() {
+ var specs = this.specs;
+ var errors = 0;
+ for(var i = 0; i < specs.length; i++) {
+ errors += specs[i].getTotalErrors();
+ }
+ return errors;
+};
+
+
+JSSpec.Runner.prototype.run = function() {
+ JSSpec.log.onRunnerStart();
+ var executor = new JSSpec.CompositeExecutor(function() {JSSpec.log.onRunnerEnd()},null,true);
+ for(var i = 0; i < this.specs.length; i++) {
+ executor.addExecutor(this.specs[i].getExecutor());
+ }
+ executor.run();
+};
+
+
+JSSpec.Runner.prototype.rerun = function(context) {
+ JSSpec.runner = new JSSpec.Runner([this.getSpecByContext(context)], JSSpec.log);
+ JSSpec.runner.run();
+};
+
+/**
+ * Logger
+ */
+JSSpec.Logger = function() {
+ this.finishedExamples = 0;
+ this.startedAt = null;
+};
+
+JSSpec.Logger.prototype.onRunnerStart = function() {
+ this._title = document.title;
+
+ this.startedAt = new Date();
+ var container = document.getElementById('jsspec_container');
+ if(container) {
+ container.innerHTML = "";
+ } else {
+ container = document.createElement("DIV");
+ container.id = "jsspec_container";
+ document.body.appendChild(container);
+ }
+
+ var title = document.createElement("DIV");
+ title.id = "title";
+ title.innerHTML = [
+ '<h1>JSSpec</h1>',
+ '<ul>',
+ JSSpec.options.rerun ? '<li>[<a href="?" title="rerun all specs">X</a>] ' + JSSpec.util.escapeTags(decodeURIComponent(JSSpec.options.rerun)) + '</li>' : '',
+ ' <li><span id="total_examples">' + JSSpec.runner.totalExamples + '</span> examples</li>',
+ ' <li><span id="total_failures">0</span> failures</li>',
+ ' <li><span id="total_errors">0</span> errors</li>',
+ ' <li><span id="progress">0</span>% done</li>',
+ ' <li><span id="total_elapsed">0</span> secs</li>',
+ '</ul>',
+ '<p><a href="http://jania.pe.kr/aw/moin.cgi/JSSpec">JSSpec homepage</a></p>',
+ ].join("");
+ container.appendChild(title);
+
+ var list = document.createElement("DIV");
+ list.id = "list";
+ list.innerHTML = [
+ '<h2>List</h2>',
+ '<ul class="specs">',
+ function() {
+ var specs = JSSpec.runner.getSpecs();
+ var sb = [];
+ for(var i = 0; i < specs.length; i++) {
+ var spec = specs[i];
+ sb.push('<li id="spec_' + specs[i].id + '_list"><h3><a href="#spec_' + specs[i].id + '">' + JSSpec.util.escapeTags(specs[i].context) + '</a> [<a href="?rerun=' + encodeURIComponent(specs[i].context) + '">rerun</a>]</h3> </li>');
+ }
+ return sb.join("");
+ }(),
+ '</ul>'
+ ].join("");
+ container.appendChild(list);
+
+ var log = document.createElement("DIV");
+ log.id = "log";
+ log.innerHTML = [
+ '<h2>Log</h2>',
+ '<ul class="specs">',
+ function() {
+ var specs = JSSpec.runner.getSpecs();
+ var sb = [];
+ for(var i = 0; i < specs.length; i++) {
+ var spec = specs[i];
+ sb.push(' <li id="spec_' + specs[i].id + '">');
+ sb.push(' <h3>' + JSSpec.util.escapeTags(specs[i].context) + ' [<a href="?rerun=' + encodeURIComponent(specs[i].context) + '">rerun</a>]</h3>');
+ sb.push(' <ul id="spec_' + specs[i].id + '_examples" class="examples">');
+ for(var j = 0; j < spec.examples.length; j++) {
+ var example = spec.examples[j];
+ sb.push(' <li id="example_' + example.id + '">');
+ sb.push(' <h4>' + JSSpec.util.escapeTags(example.name) + '</h4>');
+ sb.push(' <pre class="examples-code"><code>'+JSSpec.util.escapeTags(example.target.toString())+'</code></pre>');
+ sb.push(' </li>');
+ }
+ sb.push(' </ul>');
+ sb.push(' </li>');
+ }
+ return sb.join("");
+ }(),
+ '</ul>'
+ ].join("");
+
+ container.appendChild(log);
+
+ // add event handler for toggling
+ var specs = JSSpec.runner.getSpecs();
+ var sb = [];
+ for