Fork me on GitHub

Javascript implementation of Steven Fortune's algorithm to compute Voronoi diagrams
Demo 2: A bit of interactivity added

< Back to main page | Demo 1: measuring peformance | Demo 2: a bit of interactivity | Demo 3: Fancy tiling | Comments

Sites generator

or sites randomly (Warning: performance might suffer the more sites you add.)

Canvas

Javascript source code for this page

<script type="text/javascript" src="rhill-voronoi-core.js"></script>

...

<script type="text/javascript">
<!--
var VoronoiDemo = {

	voronoi: new Voronoi(),
	sites: [],
	diagram: null,
	margin: 100,
	canvas: null,
	bbox: {xl:0,xr:800,yt:0,yb:600},

	normalizeEventCoords: function(target,e) {
		// http://www.quirksmode.org/js/events_properties.html#position
		// =====
		if (!e) {e=self.event;}
		var x = 0;
		var y = 0;
		if (e.pageX || e.pageY) {
			x = e.pageX;
			y = e.pageY;
			}
		else if (e.clientX || e.clientY) {
			x = e.clientX+document.body.scrollLeft+document.documentElement.scrollLeft;
			y = e.clientY+document.body.scrollTop+document.documentElement.scrollTop;
			}
		// =====
		return {x:x-target.offsetLeft,y:y-target.offsetTop};
		},

	init: function() {
		var me = this;
		this.canvas = document.getElementById('voronoiCanvas');
		this.canvas.onmousemove = function(e) {
			if (!me.sites.length) {return;}
			var site = me.sites[0];
			var mouse = me.normalizeEventCoords(me.canvas,e);
			site.x = mouse.x;
			site.y = mouse.y;
			me.diagram = me.voronoi.compute(me.sites,me.bbox);
			me.render();
			};
		this.canvas.onclick = function(e) {
			var mouse = me.normalizeEventCoords(me.canvas,e);
			me.addSite(mouse.x,mouse.y);
			me.render();
			};
		this.randomSites(10,true);
		this.render();
		},

	clearSites: function() {
		// we want at least one site, the one tracking the mouse
		this.sites = [];
		this.diagram = this.voronoi.compute(this.sites, this.bbox);
		},

	randomSites: function(n,clear) {
		if (clear) {this.sites = [];}
		var xo = this.margin;
		var dx = this.canvas.width-this.margin*2;
		var yo = this.margin;
		var dy = this.canvas.height-this.margin*2;
		for (var i=0; i<n; i++) {
			this.sites.push({x:self.Math.round(xo+self.Math.random()*dx),y:self.Math.round(yo+self.Math.random()*dy)});
			}
		this.diagram = this.voronoi.compute(this.sites, this.bbox);
		},

	addSite: function(x,y) {
		this.sites.push({x:x,y:y});
		this.diagram = this.voronoi.compute(this.sites, this.bbox);
		},

	render: function() {
		var ctx = this.canvas.getContext('2d');
		// background
		ctx.globalAlpha = 1;
		ctx.beginPath();
		ctx.rect(0,0,this.canvas.width,this.canvas.height);
		ctx.fillStyle = '#fff';
		ctx.fill();
		ctx.strokeStyle = '#888';
		ctx.stroke();
		// voronoi
		if (!this.diagram) {return;}
		ctx.strokeStyle='#000';
		// edges
		var edges = this.diagram.edges;
		var iEdge = edges.length;
		if (iEdge) {
			var edge, v;
			ctx.beginPath();
			while (iEdge--) {
				edge = edges[iEdge];
				v = edge.va;
				ctx.moveTo(v.x,v.y);
				v = edge.vb;
				ctx.lineTo(v.x,v.y);
				}
			ctx.stroke();
			}
		// how many sites do we have?
		var sites = this.sites;
		var nSites = sites.length;
		if (nSites === 0) {return;}
		// highlight cell under mouse
		var cell = this.diagram.cells[this.sites[0].voronoiId];
		// there is no guarantee a Voronoi cell will exist for any
		// particular site
		if (cell !== undefined) {
			var halfedges = cell.halfedges;
			var nHalfedges = halfedges.length;
			if (nHalfedges < 3) {return;}
			var v = halfedges[0].getStartpoint();
			ctx.beginPath();
			ctx.moveTo(v.x,v.y);
			for (var iHalfedge=0; iHalfedge<nHalfedges; iHalfedge++) {
				v = halfedges[iHalfedge].getEndpoint();
				ctx.lineTo(v.x,v.y);
				}
			ctx.fillStyle = '#faa';
			ctx.fill();
			}
		// draw sites
		var site;
		ctx.beginPath();
		ctx.fillStyle = '#44f';
		for (var iSite=nSites-1; iSite>=0; iSite-=1) {
			site = sites[iSite];
			ctx.rect(site.x-2/3,site.y-2/3,2,2);
			}
		ctx.fill();
		},
	};
// -->
</script>...

<body onload="VoronoiDemo.init();">

...