/**
* gridersize, version 1.0
* (c) Tom Wiesing 2014
* Licensed under MIT
**/
/**
* @constructor
* @param {jQuery} canvas A jQuery element to use as canvas, preferably a div.
* @return
*/
var gridersize = function(canvas){
//content
this.content = [];
//zoom
this.minZoom = 0.001;
this.maxZoom = 100;
this.zoom = 1;
//canvas
this.canvas = $(canvas).css("overflow", "hidden");
//center
this.center = {
"x": 0,
"y": 0
};
//there is nothing to draw.
this._elementsDrawn = true;
};
/**
* Adds elements to be rendered.
* @param {array} elements Elements to render. An element shoudl be of the form [x, y, x_size, y_size, id, properties]
* @param {boolean} keep Should existing elements be kept on the canvas?
* @return ThisExpression
*/
gridersize.prototype.render = function(elements, keep){
//set the elements to render
var keep = (typeof keep === "boolean")?keep:true;
if(!keep){
this.content = [];
}
//iterate over and add all the elements
for(var i=0;i<elements.length;i++){
(function(element){
this.content.push(element);
}).call(this, elements[i]);
}
//elements are not currently drawn onto the canvas
this._elementsDrawn = false;
return this;
}
/**
* Makes a simple draw. Updates only positions of divs.
* @return ThisExpression
*/
gridersize.prototype.draw = function(){
var me = this;
//are the elements on the canvas yet?
if(this._elementsDrawn !== true){
this._drawElements();
}
//start updating positions
//canvas properties
var width = this.canvas.width();
var height = this.canvas.height();
var center = this.center;
var dimX = width / 2;
var dimY = height / 2;
var zoom = this.zoom;
//iterate over the children of the canvas
this.canvas.children().eq(0)
.css({
"top": height / 2,
"left": width / 2
}).children().each(function(){
var box = $(this);
var element = box.data("gridersize.position");
//compute properties of elements to render
var renderDimensions = {
//dimensions x
"minX": (element[0] - center.x)*zoom,
"maxX": (element[0] - center.x)*zoom+element[2]*zoom,
"sizeX": element[2]*zoom,
//dimensions y
"minY": (element[1] - center.y)*zoom,
"maxY": (element[1] - center.y)*zoom+element[3]*zoom,
"sizeY": element[3]*zoom
};
box.css({
"position": "absolute",
"top": renderDimensions.minY,
"left": renderDimensions.minX,
"width": renderDimensions.sizeX,
"height": renderDimensions.sizeY
});
//optimize the font size
//find the sub span and reset the font size
var sub = box.find("span").css("font-size", "1em");
//max number of adjustment steps
var fontstep = 2;
var count = 10;
while (sub.height()>box.height() || sub.width()>box.width()){
var s = parseInt(sub.css('font-size').substr(0,2));
sub.css('font-size',((s-fontstep)) + 'px').css('line-height',((s)) + 'px');
if(count-- <= 0){
break;
}
}
});
me.canvas.trigger("gridersize.move");
return this;
}
/**
* Draws all elements to the canvas
* @private
* @return ThisExpression
*/
gridersize.prototype._drawElements = function(){
var me = this;
me.canvas.trigger("gridersize.clear");
this.canvas.empty() //clear the canvas
var origin = $("<div>").css({
"position": "relative",
}).appendTo(this.canvas);
//now iterate over the boxes
for(var i=0;i<this.content.length;i++){
(function(element){
//load properties
var properties = element[5] || {};
var id = jQuery.isArray(element[4])?element[4]:[element[4]];
//create the div
var box = $("<div>")
.data("gridersize.id", id)
.data("gridersize.position", [element[0], element[1], element[2], element[3]])
.data("gridersize.clicked", false)
.data("gridersize.original", element)
.appendTo(origin);
/*
Activation / Deactivation logic
*/
box.hover(function(){
if(!box.data("gridersize.clicked")){
box
.trigger("gridersize.activate");
}
}, function(){
if(!box.data("gridersize.clicked")){
box
.trigger("gridersize.deactivate");
}
})
.on("click", function(){
//trigger a click event and a toggle event
me.canvas.trigger("gridersize.click", [box, id]);
box.trigger("gridersize.toggle");
})
.on("gridersize.toggle", function(){
//toggle this box
me.canvas.trigger("gridersize.toggle", [box, id]);
if(box.data("gridersize.clicked") === true){
box
.data("gridersize.clicked", false)
.trigger("gridersize.deactivate");
} else {
me.canvas.trigger("gridersize.focus", [box, id]);
box.parent().find("div").each(function(){
if($(this).data("gridersize.active") === true){
$(this)
.data("gridersize.clicked", false)
.trigger("gridersize.deactivate");
}
});
box
.data("gridersize.clicked", true)
.trigger("gridersize.activate");
}
})
.on("gridersize.activate", function(){
box.data("gridersize.active", true);
me.canvas.trigger("gridersize.focus", [box, id]);
})
.on("gridersize.deactivate", function(){
box.data("gridersize.active", false);
me.canvas.trigger("gridersize.unfocus", [box, id]);
})
.data("gridersize.active", false);
/*
Box border
Determines which borders of the box are rendered.
border: boolean | [top, right, bottom, left] || true;
border-style: cssString || "1px solid black",
border-hover-style: cssString || "1px solid black"
*/
var borderStyle = properties["border-style"] || "1px solid black";
var borderHoverStyle = properties["border-hover-style"] || "1px solid black";
if(!properties.hasOwnProperty("border")){
properties.border = true;
}
if(typeof properties.border == "boolean"){
properties.border = [properties.border, properties.border, properties.border, properties.border];
}
if(properties.border[0]){
box.css({
"border-top": borderStyle
}).on("gridersize.activate", function(){
box.css("border-top", borderHoverStyle);
}).on("gridersize.deactivate", function(){
box.css("border-top", borderStyle);
});
}
if(properties.border[1]){
box.css({
"border-right": borderStyle
}).on("gridersize.activate", function(){
box.css("border-right", borderHoverStyle);
}).on("gridersize.deactivate", function(){
box.css("border-right", borderStyle);
});
}
if(properties.border[2]){
box.css({
"border-bottom": borderStyle
}).on("gridersize.activate", function(){
box.css("border-bottom", borderHoverStyle);
}).on("gridersize.deactivate", function(){
box.css("border-bottom", borderStyle);
});
}
if(properties.border[3]){
box.css({
"border-left": borderStyle
}).on("gridersize.activate", function(){
box.css("border-left", borderHoverStyle);
}).on("gridersize.deactivate", function(){
box.css("border-left", borderStyle);
});
}
/*
Box css class
Also handles activation
class: cssClassString || "box"
active-class: cssClassString || "active"
*/
var cssClass = properties["class"] || "box";
var activeClass = properties["active-class"] || "active";
box.addClass(cssClass)
.on("gridersize.activate", function(){
box.addClass(activeClass);
})
.on("gridersize.deactivate", function(){
box.removeClass(activeClass);
});
/*
Set the content
*/
var text = properties["label"] || false;
if(typeof text == "string"){
box.append($("<span>").text(text));
}
}).call(this, this.content[i]);
}
//elements are now drawn onto the canvas
this._elementsDrawn = true;
me.canvas.trigger("gridersize.draw"); //elements have been re-drawn
return this;
}
/**
* Sets the zoom level.
* @param {number} level Factor to set the zoom to.
* @return ThisExpression
*/
gridersize.prototype.setZoom = function(level){
//set the zoom level
if(typeof level == "undefined"){
this.showAll();
}
this.zoom = level;
if(this.zoom > this.maxZoom){
this.zoom = this.maxZoom;
}
if(this.zoom < this.minZoom){
this.zoom = this.minZoom;
}
return this;
}
/**
* Description
* @param {number} x X-coordinate of the center.
* @param {number} y Y-coordinate of the center.
* @param {boolean} relative If set to true, set coordinates relatively.
* @return ThisExpression
*/
gridersize.prototype.setCenter = function(x, y, relative){
//set the center
var relative = (relative === true);
if(relative){
this.center.x += x;
this.center.y += y;
} else {
this.center.x = x;
this.center.y = y;
}
return this;
}
/**
* Shows a specific Region.
* @param {number} minX Minimal X Coordinate to show.
* @param {number} maxX Maximal X Coordinate to show.
* @param {number} minY Minimal Y Coordinate to show
* @param {number} maxY Maximal Y Coordinate to show
* @param {number} [margin=5] Margin to use. Defaults to 5.
* @return ThisExpression
*/
gridersize.prototype.showRegion = function(minX, maxX, minY, maxY, margin){
if(typeof margin !== "number"){
var margin = 5;
}
//center in the middle of the region
var centerX = minX+((maxX - minX)/ 2);
var centerY = minY+((maxY - minY)/ 2);
//compute zoom and have a margin of 5 units
var width = this.canvas.width();
var height = this.canvas.height();
var zoomX = width/(maxX - minX + margin);
var zoomY = height/(maxY - minY + margin);
this
.setCenter(centerX, centerY)
.setZoom(Math.min(zoomX, zoomY))
.draw();
return this;
}
/**
* Shows everything that is currently on the screen.
* Changes zoom and center.
* @param {number} [margin=5] Margin to use.
* @return ThisExpression
*/
gridersize.prototype.showAll = function(margin){
var minX = Infinity;
var maxX = -Infinity;
var minY = Infinity;
var maxY = -Infinity;
//get max and mins correctly
for(var i=0;i<this.content.length;i++){
(function(element){
minX = Math.min(element[0], minX);
maxX = Math.max(element[0]+element[2], maxX);
minY = Math.min(element[1], minY);
maxY = Math.max(element[1]+element[3], maxY);
}).call(this, this.content[i]);
}
this.showRegion(minX, maxX, minY, maxY, margin);
return this;
}
/**
* Adds keyboard shotcuts
* @return ThisExpression
*/
gridersize.prototype.keys = function(){
var me = this;
var deltaZoom = 2;
var deltaCenter = 10;
//Listen to key events
$("body").on("keydown", function(evt){
var key = evt.keyCode;
if(key == 107 || key == 187){
//plus
me
.setZoom(me.zoom+0.1*deltaZoom)
.draw();
} else if(key == 109 || key == 189){
//minus
me.setZoom(me.zoom-0.1*deltaZoom).draw();
} else if(key == 38){
//top
me.setCenter(0, deltaCenter/me.zoom, true).draw();
} else if(key == 39){
//left
me.setCenter(-deltaCenter/me.zoom, 0, true).draw();
} else if(key == 40){
//down
me.setCenter(0, -deltaCenter/me.zoom, true).draw();
} else if(key == 37){
//right
me.setCenter(deltaCenter/me.zoom, 0, true).draw();
}
})
return this;
}
/**
* Enables mouse shortcuts
* (drag and drop)
* @return ThisExpression
*/
gridersize.prototype.mouse = function(){
//mouse dragging
var dragStart = false;
var dragOrigin = false;
var zoom = this.zoom;
var me = this;
var mouseMove = function(event){
zoom = me.zoom;
if(dragStart){
me
.setCenter(
dragStart[0] + ((dragOrigin[0] - event.pageX) / zoom),
dragStart[1] + ((dragOrigin[1] - event.pageY) / zoom)
)
.draw();
}
me.canvas.trigger("dragmove");
};
var mouseDown = function(event){
mouseEnd();
if(event.which == 1){
//only trigger on the left mouse click
dragStart = [me.center.x, me.center.y];
dragOrigin = [event.pageX, event.pageY];
}
me.canvas.trigger("dragstart");
//set moveable cursor
$('html,body').css('cursor','move');
};
var mouseEnd = function(){
dragStart = undefined;
dragOrigin = undefined;
me.canvas.trigger("dragstop");
//set normal cursor
$('html,body').css('cursor','initial');
};
this.canvas
.on("mousemove", mouseMove)
.on("mousemove", "*", mouseMove)
.on("mousedown", mouseDown)
.on("mousedown", "*", mouseDown)
.on("mouseleave mouseup", mouseEnd)
.on("mouseup", "*", mouseEnd);
//mouse wheel
this.canvas.on("mousewheel", function(e){
//add 0.1*the delta Y
me
.setZoom(me.zoom+0.1*e.deltaY)
.draw();
})
return this;
}
/**
* Finds an element given its Id.
* @param {string} id Id to find.
* @return jQuery
*/
gridersize.prototype.findElementById = function(id){
var allElements = this.canvas.find("div");
return allElements.filter(function(){
try{
return $(this).data("gridersize.id").indexOf(id) !== -1;
}catch(e){
return false;
}
});
}
/**
* Finds an element given its Id and activates it.
* @param {string} id Id to find.
* @return jQuery
*/
gridersize.prototype.activateElementById = function(id){
var element = this.findElementById(id);
if(!element.data("gridersize.clicked")){
element.trigger("gridersize.toggle");
}
return element;
}
/**
* Deactivates all currently active elements.
* @return ThisExpression
*/
gridersize.prototype.deactivateAll = function(){
this.canvas.find("div").each(function(i, e){
var element = $(this);
if(element.data("gridersize.clicked")){
element.trigger("gridersize.toggle");
}
});
return this;
}
/**
* @static Version number
*/
gridersize.version = "1.0";
gridersize.prototype.doTheThing = function(){
alert("I'm just doing my thing. Move along. ");
}