|
// Open Hack day 2012 ! |
|
|
|
/* AJAX methods */ |
|
var xmlhttp; |
|
function loadXMLDoc(url, callback) { |
|
xmlhttp = null; |
|
if (window.XMLHttpRequest) {// code for all new browsers |
|
xmlhttp = new XMLHttpRequest(); |
|
} else if (window.ActiveXObject) {// code for IE5 and IE6 |
|
xmlhttp = new ActiveXObject("Microsoft.XMLHTTP"); |
|
} |
|
if (xmlhttp != null) { |
|
xmlhttp.onreadystatechange = callback; |
|
xmlhttp.open("GET", url, true); |
|
xmlhttp.send(null); |
|
} else { |
|
alert("Your browser does not support XMLHTTP."); |
|
} |
|
} |
|
|
|
|
|
/*******************************************************************/ |
|
// Constructor for Tag objects to hold data for all drawn objects. |
|
// For now they will just be defined as rectangles. |
|
function Tag(x, y, fill, txt) { |
|
// This is a very simple and unsafe constructor. All we're doing is checking if the values exist. |
|
// "x || 0" just means "if there is a value for x, use that. Otherwise use 0." |
|
// But we aren't checking anything else! We could put "Lalala" for the value of x |
|
this.x = x || 0; |
|
this.y = y || 0; |
|
this.w = 10; |
|
this.h = 30; |
|
this.fill = fill || '#AAAAAA'; |
|
this.text = txt || 'default'; |
|
} |
|
|
|
// Draws this Tag to a given context |
|
Tag.prototype.draw = function(ctx) { |
|
ctx.font = "30px Georgia"; |
|
ctx.fillStyle = '#000000'; |
|
ctx.fillText(this.text, this.x, this.y + this.h); |
|
this.w = ctx.measureText(this.text).width; |
|
console.log(this.x); |
|
} |
|
|
|
// Determine if a point is inside the Tag's bounds |
|
Tag.prototype.contains = function(mx, my) { |
|
// All we have to do is make sure the Mouse X,Y fall in the area between |
|
// the Tag's X and (X + Height) and its Y and (Y + Height) |
|
return (this.x <= mx) && (this.x + this.w >= mx) && (this.y <= my) |
|
&& (this.y + this.h >= my); |
|
} |
|
|
|
function TagCloud(x, y, rad, col) { |
|
this.x = x; |
|
this.y = y; |
|
this.radius = rad; |
|
this.fill = col; |
|
} |
|
|
|
TagCloud.prototype.draw = function(ctx) { |
|
ctx.fillStyle = this.fill; |
|
//draw a circle |
|
ctx.beginPath(); |
|
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2, true); |
|
ctx.closePath(); |
|
ctx.fill(); |
|
} |
|
|
|
TagCloud.prototype.contains = function(mx, my) { |
|
|
|
// Since tag cloud is a circle, we need to check if the distance of given |
|
// point from the center of the tag cloud is less than or equal to the radius |
|
dx = this.x - mx; |
|
dy = this.y - my; |
|
return dx * dx + dy * dy <= this.radius * this.radius; |
|
} |
|
|
|
function CloudButton(x, y, w, h, txt, col) { |
|
this.x = x; |
|
this.y = y; |
|
this.h = h; |
|
this.w = w; |
|
this.text = txt; |
|
this.fill = col; |
|
} |
|
|
|
CloudButton.prototype.draw = function(ctx) { |
|
ctx.fillStyle = this.fill; |
|
ctx.fillRect(this.x, this.y, this.w, this.h); |
|
ctx.fillStyle = "#FFFFFF"; |
|
ctx.font = "30px Georgia"; |
|
ctx.fillText(this.text, this.x, this.y + this.h); |
|
} |
|
|
|
//Determine if a point is inside the Tag's bounds |
|
CloudButton.prototype.contains = function(mx, my) { |
|
// All we have to do is make sure the Mouse X,Y fall in the area between |
|
// the Tag's X and (X + Height) and its Y and (Y + Height) |
|
return (this.x <= mx) && (this.x + this.w >= mx) && (this.y <= my) |
|
&& (this.y + this.h >= my); |
|
} |
|
|
|
CanvasState.prototype.fadeOut = function(e){ |
|
var alpha = 1.0; // full opacity |
|
var canvas = this.canvas; |
|
var context = this.ctx; |
|
interval = setInterval(function () { |
|
//canvas.width = canvas.width; // Clears the canvas |
|
context.fillStyle = "rgba(255, 0, 0, " + alpha + ")"; |
|
context.font = "italic 20pt Arial"; |
|
context.fillText("added", 50, 50); |
|
alpha = alpha - 0.05; // decrease opacity (fade out) |
|
if (alpha < 0) { |
|
//this.canvas.width = this.canvas.width; // Clears the canvas |
|
|
|
clearInterval(interval); |
|
} |
|
}, 50); |
|
|
|
} |
|
|
|
function CanvasState(canvas) { |
|
// **** First some setup! **** |
|
|
|
this.canvas = canvas; |
|
this.width = canvas.width; |
|
this.height = canvas.height; |
|
this.ctx = canvas.getContext('2d'); |
|
// This complicates things a little but but fixes mouse co-ordinate problems |
|
// when there's a border or padding. See getMouse for more detail |
|
var stylePaddingLeft, stylePaddingTop, styleBorderLeft, styleBorderTop; |
|
if (document.defaultView && document.defaultView.getComputedStyle) { |
|
this.stylePaddingLeft = parseInt(document.defaultView.getComputedStyle( |
|
canvas, null)['paddingLeft'], 10) || 0; |
|
this.stylePaddingTop = parseInt(document.defaultView.getComputedStyle( |
|
canvas, null)['paddingTop'], 10) || 0; |
|
this.styleBorderLeft = parseInt(document.defaultView.getComputedStyle( |
|
canvas, null)['borderLeftWidth'], 10) || 0; |
|
this.styleBorderTop = parseInt(document.defaultView.getComputedStyle( |
|
canvas, null)['borderTopWidth'], 10) || 0; |
|
} |
|
// Some pages have fixed-position bars (like the stumbleupon bar) at the top or left of the page |
|
// They will mess up mouse coordinates and this fixes that |
|
var html = document.body.parentNode; |
|
this.htmlTop = html.offsetTop; |
|
this.htmlLeft = html.offsetLeft; |
|
|
|
// **** Keep track of state! **** |
|
|
|
this.valid = false; // when set to false, the canvas will redraw everything |
|
this.Tags = []; // Collection of tags in main cloud |
|
this.myTags = []; // Collection of tags in my cloud |
|
this.cloudButtons = []; |
|
|
|
/* Ads cloud */ |
|
this.adsCloudX = 500; |
|
this.adsCloudY = 400; |
|
this.adsCloudRadius = 250; |
|
console.log("Cloud center x: " + this.adsCloudX); |
|
console.log("Cloud radius: " + this.adsCloudRadius); |
|
|
|
this.adsCloud = new TagCloud(this.adsCloudX, this.adsCloudY, |
|
this.adsCloudRadius, '#99CCFF'); |
|
/* My ads cloud */ |
|
this.myCloudX = 1000; |
|
this.myCloudY = 400; |
|
this.myCloudRadius = 200; |
|
this.myCloud = new TagCloud(this.myCloudX, this.myCloudY, |
|
this.myCloudRadius, '#E9CCFC'); |
|
|
|
this.dragging = false; // Keep track of when we are dragging |
|
// the current selected object. In the future we could turn this into an array for multiple selection |
|
this.selection = null; |
|
this.dragoffx = 0; // See mousedown and mousemove events for explanation |
|
this.dragoffy = 0; |
|
|
|
// **** Then events! **** |
|
|
|
// This is an example of a closure! |
|
// Right here "this" means the CanvasState. But we are making events on the Canvas itself, |
|
// and when the events are fired on the canvas the variable "this" is going to mean the canvas! |
|
// Since we still want to use this particular CanvasState in the events we have to save a reference to it. |
|
// This is our reference! |
|
var myState = this; |
|
|
|
//fixes a problem where double clicking causes text to get selected on the canvas |
|
canvas.addEventListener('selectstart', function(e) { |
|
e.preventDefault(); |
|
return false; |
|
}, false); |
|
// Up, down, and move are for dragging |
|
canvas.addEventListener('mousedown', function(e) { |
|
var mouse = myState.getMouse(e); |
|
var mx = mouse.x; |
|
var my = mouse.y; |
|
var Tags = myState.Tags; |
|
var l = Tags.length; |
|
for ( var i = l - 1; i >= 0; i--) { |
|
if (Tags[i].contains(mx, my)) { |
|
var mySel = Tags[i]; |
|
// Keep track of where in the object we clicked |
|
// so we can move it smoothly (see mousemove) |
|
myState.dragoffx = mx - mySel.x; |
|
myState.dragoffy = my - mySel.y; |
|
myState.dragging = true; |
|
myState.selection = mySel; |
|
myState.valid = false; |
|
return; |
|
} |
|
} |
|
|
|
// havent returned means we have failed to select anything. |
|
// If there was an object selected, we deselect it |
|
if (myState.selection) { |
|
myState.selection = null; |
|
myState.valid = false; // Need to clear the old selection border |
|
} |
|
}, true); |
|
canvas.addEventListener('mousemove', function(e) { |
|
if (myState.dragging) { |
|
var mouse = myState.getMouse(e); |
|
// We don't want to drag the object by its top-left corner, we want to drag it |
|
// from where we clicked. Thats why we saved the offset and use it here |
|
myState.selection.x = mouse.x - myState.dragoffx; |
|
myState.selection.y = mouse.y - myState.dragoffy; |
|
myState.valid = false; // Something's dragging so we must redraw |
|
} |
|
}, true); |
|
canvas.addEventListener('mouseup', function(e) { |
|
if (myState.dragging) { |
|
var mouse = myState.getMouse(e); |
|
var mx = mouse.x; |
|
var my = mouse.y; |
|
var cloud = myState.myCloud; |
|
if (cloud.contains(mx, my)) |
|
{ |
|
console.log(myState.selection.text + " dropped in my cloud"); |
|
loadXMLDoc('http://localhost:3000/tag?=' + myState.selection.text, null); |
|
} |
|
} else { |
|
var mouse = myState.getMouse(e); |
|
var mx = mouse.x; |
|
var my = mouse.y; |
|
|
|
var len = myState.cloudButtons.length; |
|
for ( var i = len - 1; i >= 0; i--) { |
|
if (myState.cloudButtons[i].contains(mx, my)) { |
|
console.log(myState.cloudButtons[i].text + " clicked."); |
|
loadCloudTags(myState.cloudButtons[i].text); |
|
} |
|
} |
|
} |
|
myState.dragging = false; |
|
}, true); |
|
// double click for making new Tags |
|
canvas.addEventListener('dblclick', function(e) { |
|
//var mouse = myState.getMouse(e); |
|
//myState.addTag(new Tag(mouse.x - 10, mouse.y - 10, 20, 20, |
|
// 'rgba(0,255,0,.6)')); |
|
}, true); |
|
|
|
// **** Options! **** |
|
|
|
this.selectionColor = '#CC0000'; |
|
this.selectionWidth = 2; |
|
this.interval = 30; |
|
setInterval(function() { |
|
myState.draw(); |
|
}, myState.interval); |
|
} |
|
|
|
CanvasState.prototype.addTag = function(tag) { |
|
var tagX = Math.floor((Math.random() * (this.adsCloudRadius - 100)) + 1); |
|
if (Math.floor((Math.random() * 10) + 1) > 5) |
|
tagX = this.adsCloudX + tagX; // Subtracted constant to prevent text from getting out of the cloud |
|
else |
|
tagX = this.adsCloudX - tagX; |
|
|
|
var tagY = Math.floor((Math.random() * (this.adsCloudRadius - 100)) + 1); |
|
if (Math.floor((Math.random() * 10) + 1) > 5) |
|
tagY = this.adsCloudY + tagY; |
|
else |
|
tagY = this.adsCloudY - tagY; |
|
|
|
console.log(tagX); |
|
this.Tags.push(new Tag(tagX, tagY, 'rgba(127, 255, 212, .5)', tag)); |
|
this.valid = false; |
|
} |
|
|
|
CanvasState.prototype.addMyTag = function(tag) { |
|
var tagX = Math.floor((Math.random() * (this.myCloudRadius - 50)) + 1); |
|
if (Math.floor((Math.random() * 10) + 1) > 5) |
|
tagX = this.myCloudX + tagX; // Subtracted constant to prevent text from getting out of the cloud |
|
else |
|
tagX = this.myCloudX - tagX; |
|
|
|
var tagY = Math.floor((Math.random() * (this.myCloudRadius - 50)) + 1); |
|
if (Math.floor((Math.random() * 10) + 1) > 5) |
|
tagY = this.myCloudY + tagY; |
|
else |
|
tagY = this.myCloudY - tagY; |
|
|
|
console.log(tagX); |
|
console.log(tagY); |
|
console.log("adding my tag: " + tag); |
|
this.myTags.push(new Tag(tagX, tagY, 'rgba(127, 255, 212, .5)', tag)); |
|
this.valid = false; |
|
} |
|
|
|
CanvasState.prototype.addCloud = function(cloud) { |
|
var count = this.cloudButtons.length; |
|
this.cloudButtons.push(new CloudButton(10, count * 60 + 100, 200, 50, |
|
cloud, "#FF6600")); |
|
this.valid = false; |
|
} |
|
|
|
CanvasState.prototype.clearMainCloud = function() { |
|
this.Tags = []; |
|
} |
|
|
|
CanvasState.prototype.clearMyCloud = function() { |
|
this.myTags = []; |
|
} |
|
|
|
CanvasState.prototype.clear = function() { |
|
this.ctx.clearRect(0, 0, this.width, this.height); |
|
} |
|
|
|
// While draw is called as often as the INTERVAL variable demands, |
|
// It only ever does something if the canvas gets invalidated by our code |
|
CanvasState.prototype.draw = function() { |
|
// if our state is invalid, redraw and validate! |
|
if (!this.valid) { |
|
var ctx = this.ctx; |
|
var Tags = this.Tags; |
|
this.clear(); |
|
|
|
for ( var i = 0; i < this.cloudButtons.length; i++) { |
|
var button = this.cloudButtons[i]; |
|
button.draw(ctx); |
|
} |
|
|
|
/* Draw the tag clouds */ |
|
this.adsCloud.draw(ctx); |
|
this.myCloud.draw(ctx); |
|
// ** Add stuff you want drawn in the background all the time here ** |
|
|
|
// draw my Tags |
|
var len = this.myTags.length; |
|
for ( var i = 0; i < len; i++) { |
|
var Tag = this.myTags[i]; |
|
// We can skip the drawing of elements that have moved off the screen: |
|
if (Tag.x > this.width || Tag.y > this.height || Tag.x + Tag.w < 0 |
|
|| Tag.y + Tag.h < 0) |
|
continue; |
|
this.myTags[i].draw(ctx); |
|
} |
|
|
|
// draw all Tags |
|
var l = Tags.length; |
|
for ( var i = 0; i < l; i++) { |
|
var Tag = Tags[i]; |
|
// We can skip the drawing of elements that have moved off the screen: |
|
if (Tag.x > this.width || Tag.y > this.height || Tag.x + Tag.w < 0 |
|
|| Tag.y + Tag.h < 0) |
|
continue; |
|
Tags[i].draw(ctx); |
|
} |
|
|
|
|
|
// draw selection |
|
// right now this is just a stroke along the edge of the selected Tag |
|
if (this.selection != null) { |
|
ctx.strokeStyle = this.selectionColor; |
|
ctx.lineWidth = this.selectionWidth; |
|
var mySel = this.selection; |
|
ctx.strokeRect(mySel.x, mySel.y, mySel.w, mySel.h); |
|
} |
|
|
|
// ** Add stuff you want drawn on top all the time here ** |
|
|
|
this.valid = true; |
|
} |
|
} |
|
|
|
// Creates an object with x and y defined, set to the mouse position relative to the state's canvas |
|
// If you wanna be super-correct this can be tricky, we have to worry about padding and borders |
|
CanvasState.prototype.getMouse = function(e) { |
|
var element = this.canvas, offsetX = 0, offsetY = 0, mx, my; |
|
|
|
// Compute the total offset |
|
if (element.offsetParent !== undefined) { |
|
do { |
|
offsetX += element.offsetLeft; |
|
offsetY += element.offsetTop; |
|
} while ((element = element.offsetParent)); |
|
} |
|
|
|
// Add padding and border style widths to offset |
|
// Also add the <html> offsets in case there's a position:fixed bar |
|
offsetX += this.stylePaddingLeft + this.styleBorderLeft + this.htmlLeft; |
|
offsetY += this.stylePaddingTop + this.styleBorderTop + this.htmlTop; |
|
|
|
mx = e.pageX - offsetX; |
|
my = e.pageY - offsetY; |
|
|
|
// We return a simple javascript object (a hash) with x and y defined |
|
return { |
|
x : mx, |
|
y : my |
|
}; |
|
} |
|
|
|
|
|
// If you dont want to use <body onLoad='init()'> |
|
// You could uncomment this init() reference and place the script reference inside the body tag |
|
//init(); |
|
var cloudsObj; |
|
var canvasState; |
|
|
|
|
|
function displayClouds() { |
|
if (xmlhttp.readyState == 4) {// 4 = "loaded" |
|
if (xmlhttp.status == 200) {// 200 = OK |
|
var json_clouds = xmlhttp.responseText; |
|
console.log(json_clouds); |
|
cloudsObj = JSON.parse(json_clouds); |
|
console.log(cloudsObj); |
|
for (var i = 0; i < cloudsObj.length; i++) { |
|
console.log(cloudsObj[i].cloud.name); |
|
canvasState.addCloud(cloudsObj[i].cloud.name); |
|
} |
|
} else { |
|
alert("Problem retrieving data"); |
|
} |
|
} |
|
} |
|
|
|
function displayTags() { |
|
if (xmlhttp.readyState == 4) {// 4 = "loaded" |
|
if (xmlhttp.status == 200) {// 200 = OK |
|
var json_cloudTags = xmlhttp.responseText; |
|
var cloudTags = JSON.parse(json_cloudTags); |
|
for (var i = 0; i < cloudTags.length; i++) { |
|
console.log("Tag: " + cloudTags[i].tag.name); |
|
canvasState.addTag(cloudTags[i].tag.name); |
|
} |
|
} else { |
|
alert("Problem retrieving data"); |
|
} |
|
} |
|
} |
|
|
|
function displayMyTags() |
|
{ |
|
if (xmlhttp.readyState == 4) {// 4 = "loaded" |
|
if (xmlhttp.status == 200) {// 200 = OK |
|
var json_cloudTags = xmlhttp.responseText; |
|
var cloudTags = JSON.parse(json_cloudTags); |
|
for (var i = 0; i < cloudTags.length; i++) { |
|
console.log("Tag: " + cloudTags[i].tag.name); |
|
canvasState.addMyTag(cloudTags[i].tag.name); |
|
} |
|
} else { |
|
alert("Problem retrieving data"); |
|
} |
|
} |
|
|
|
} |
|
|
|
function loadMyTags() |
|
{ |
|
canvasState.clearMyCloud(); |
|
// Fetch cloud tags |
|
//loadXMLDoc('http://localhost/mytags', displayMyTags); |
|
var json_myTags = '[{"tag":{"name":"Bangalore","id":1,"updated_at":"2012-08-11T18:16:04Z","description":"","cloud_id":1,"created_at":"2012-08-11T12:54:59Z"}},{"tag":{"name":"Hyderabad","id":4,"updated_at":"2012-08-11T19:50:24Z","description":"blah","cloud_id":1,"created_at":"2012-08-11T19:50:24Z"}}]'; |
|
var myTags = JSON.parse(json_myTags); |
|
for (var i = 0; i < myTags.length; i++) { |
|
console.log("Tag: " + myTags[i].tag.name); |
|
canvasState.addMyTag(myTags[i].tag.name); |
|
} |
|
} |
|
|
|
function loadCloudTags(cloud) { |
|
canvasState.clearMainCloud(); |
|
// Fetch cloud tags |
|
|
|
// Fetch tags for a cloud |
|
loadXMLDoc('http://localhost/' + cloud +'/tags', displayTags); |
|
/*var json_cloudTags = '[{"tag":{"name":"Bangalore","id":1,"updated_at":"2012-08-11T18:16:04Z","description":"","cloud_id":1,"created_at":"2012-08-11T12:54:59Z"}},{"tag":{"name":"Hyderabad","id":4,"updated_at":"2012-08-11T19:50:24Z","description":"blah","cloud_id":1,"created_at":"2012-08-11T19:50:24Z"}}]'; |
|
var cloudTags = JSON.parse(json_cloudTags); |
|
for (var i = 0; i < cloudTags.length; i++) { |
|
console.log("Tag: " + cloudTags[i].tag.name); |
|
canvasState.addTag(cloudTags[i].tag.name); |
|
}*/ |
|
} |
|
|
|
function init() { |
|
canvasState = new CanvasState(document.getElementById('canvas1')); |
|
|
|
loadXMLDoc('http://localhost/clouds', displayClouds); |
|
/* |
|
json_clouds = '[{"cloud":{"name":"Locations","id":1,"updated_at":"2012-08-11T16:46:43Z","created_at":"2012-08-11T16:46:43Z"}},{"cloud":{"name":"Categories","id":2,"updated_at":"2012-08-11T16:46:57Z","created_at":"2012-08-11T16:46:57Z"}},{"cloud":{"name":"Deals","id":3,"updated_at":"2012-08-11T16:57:17Z","created_at":"2012-08-11T16:57:17Z"}},{"cloud":{"name":"Professions","id":4,"updated_at":"2012-08-11T16:57:27Z","created_at":"2012-08-11T16:57:27Z"}},{"cloud":{"name":"Hobbies","id":5,"updated_at":"2012-08-11T16:57:34Z","created_at":"2012-08-11T16:57:34Z"}}]'; |
|
cloudsObj = JSON.parse(json_clouds); |
|
for (var i = 0; i < cloudsObj.length; i++) { |
|
console.log(cloudsObj[i].cloud.name); |
|
canvasState.addCloud(cloudsObj[i].cloud.name); |
|
}*/ |
|
loadMyTags(); |
|
|
|
} |
|
|
|
// Now go make something amazing! |