Skip to content

Instantly share code, notes, and snippets.

@Qard
Created July 16, 2011 20:55
Show Gist options
  • Save Qard/1086780 to your computer and use it in GitHub Desktop.
Save Qard/1086780 to your computer and use it in GitHub Desktop.
Vennly.js rewrite
/**
* Some basic rewriting to connect the functions via prototype.
* It's faster that way as the functions are only addressed to memory once.
*
* Also stores the canvas and context as instance properties rather than passing them through everything.
* Again, it is faster that way.
*/
/**
*
* Copyright (c) 2011 Dustin Senos
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*
*
* Vennly generates two-sphere Venn diagrams within the passed in <canvas> element
*
* Calculations were adapted from Florian Finkernagel's pyvenn: http://code.google.com/p/pyvenn/
*
* @param id The ID of the canvas element we're using
* @param option Object containing overrides of the default options
*
* @return object Object containing the public function render();
*
*/
var Vennly = function(id, options) {
if ( ! (this instanceof Vennly)) {
return new Vennly(id, options);
}
// Merge defaults with options.
this.options = options;
$.extend(this.options, {
color_one : "rgb(90, 225, 90)",
color_two : "rgb(74, 199, 214)",
composite_operation : "darker"
});
// Find canvas.
this.canvas = document.getElementById(id);
// Check to make sure the passed element is a canvas object
// Use Modernizr to detect if the browser supports the canvas
// element before calling Vennly
if ( typeof this.canvas.getContext === 'undefined' )
{
throw new Error('You must pass a canvas element');
}
// Store the 2D context of the canvas element
this.ctx = this.canvas.getContext('2d');
};
/**
* Starts the rendering proccess of the Venn diagram into the <canvas> element
*
* @param sphereOneRadius Radius of the left most sphere
* @param sphereTwoRadius Radius of the right most sphere
* @param percentOfOverlap Percent of the two spheres overlap. 0 is none, 100 is complete
* @param userOptions Optional: An object containing new option values
*
* @return void
*
*/
Vennly.prototype.render = function(sphereOneRadius, sphereTwoRadius, percentOfOverlap, userOptions)
{
// Bound check our radii and overlap percent
if (sphereOneRadius < 0 || sphereTwoRadius < 0)
{
throw new Error('Sphere radii must be a positive number');
}
if (percentOfOverlap < 0 || percentOfOverlap > 100)
{
throw new Error('Percent of overlap must be within 0-100');
}
// Update the default options with any options passed in
// This uses JQuery's extend() method
$.extend(this.options, userOptions || {});
var overlap = this.calculateOverlap(sphereOneRadius, sphereTwoRadius, percentOfOverlap);
if (overlap < 0)
{
throw new Error('Error calculating overlap');
}
this.clearCanvas();
this.plotSpheres(sphereOneRadius, sphereTwoRadius, overlap);
};
};
/**
* Plot Spheres
*
* As this method is private to the outside world we rely on the bound
* checking completed in render() above
*
* @param sphereOneRadius Radius of the the left most sphere
* @param sphereTwoRadius Radius of the right most sphere
* @param distanceBetweenSpheres Distance between the two spheres
*
* @return void
*
*/
Vennly.prototype.plotSpheres = function(sphereOneRadius, sphereTwoRadius, distanceBetweenSpheres)
{
// Find out how much room we need to fit our diagram
var coordinate_width = (sphereOneRadius + distanceBetweenSpheres + sphereTwoRadius);
var coordinate_height = coordinate_width;
// Calculate how much room we have in our canvas element
var image_width = this.canvas.width;
var image_height = Math.floor(Math.ceil(image_width * coordinate_height / coordinate_width));
// Scale the context to fit within the canvas element
this.ctx.scale(image_width / coordinate_width, image_height / coordinate_height);
this.ctx.translate(sphereOneRadius, coordinate_height / 2.0);
// Draw Sphere One
this.ctx.fillStyle = this.options.color_one;
this.ctx.beginPath();
this.ctx.arc(0, 0, sphereOneRadius / 1.0, 0, Math.PI * 2);
this.ctx.closePath();
this.ctx.fill();
// Change the blending method
this.ctx.globalCompositeOperation = this.options.composite_operation;
// Draw Sphere Two
this.ctx.fillStyle = this.options.color_two;
this.ctx.beginPath();
this.ctx.arc(distanceBetweenSpheres, 0, sphereTwoRadius / 1.0, 0, Math.PI * 2);
this.ctx.closePath();
this.ctx.fill();
};
/**
* Calculate Overlap
*
* Calulate the overlap of the two spheres. This is used to determine how far
* sphere two should be drawn away from sphere one. Vennly renders the smaller
* sphere against the edge of the larger sphere, not in the center. For this reason
* we return the Math.max of the two calulations
*
* @param sphereOneRadius Radius of the the left most sphere
* @param sphereTwoRadius Radius of the right most sphere
* @param percentOfOverlap Distance between the two spheres in percentage: 0-100
*
* @return Number The distance the two spheres should be apart
*
*/
Vennly.prototype.calculateOverlap = function(sphereOneRadius, sphereTwoRadius, percentOfOverlap)
{
return Math.max(Math.abs(sphereOneRadius - sphereTwoRadius), (sphereOneRadius + sphereTwoRadius) * (percentOfOverlap * 0.01));
};
/**
* Clear Canvas
*
* Clears and resets the passed in canvas and canvas context
*
* @return void
*
*/
Vennly.prototype.clearCanvas = function()
{
// Reset the canvas context
this.ctx.setTransform(1,0,0,1,0,0);
this.ctx.scale(1, 1);
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
// Reset the canvas as shown on http://diveintohtml5.org/canvas.html#divingin
var w = this.canvas.width;
this.canvas.width = 1;
this.canvas.width = w;
};
@Qard
Copy link
Author

Qard commented Jul 17, 2011

That's just because of the self-executing function you wrapped everything in. All the functions are defined in the context of the anonymous self-executing function, and the result is getting assigned to the Vennly variable so that stuff only occurs once. It's a decent method of isolating private properties, but is generally not encouraged because that sort of design expects that out-of-bounds variables are accessible in the current context. It creates a sort of bubble that becomes inaccessible to other code, which can make debugging or extending difficult. I suppose it's more a matter of preference than anything really.

Another tip;

(function(){
window.Vennly = vnly;
})();

is faster than;

var Vennly = (function(){
return vnly;
})();

When using "var" the VM needs to do a check to determine the current context. While window.Vennly just applies the property to the window object, which is available in all contexts so no check is needed. Also, returning from a function adds a bit of overhead from the state of function getting packaged and the garbage collection on it disabled so the context remains accessible in memory after the function finishes executing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment