
/* Some globals that have to be easily editable even for non-JS peeps */
	
var sizeText = 'In 1956 parliament was increased from 100 to 150 seats.';
var standardScale = 3;			// default height for seat graphs; in pixels per seat
var standardstartyear = 1981;		// seat graphs start here by default
var defaultColor = '#333333'; 		// for parties without a defined colour
var positionFromTop = 25;		// for top coordinate right column fact sheet		


/* The initialisation function is at the bottom of this script. */

function initialiseApp() {

	var openFigures = 0;

	/* 
		create global dblclick handler for calling up
		party fact sheets (see specialFactSheet)
	*/	

	document.ondblclick = function (e) {create.specialFactSheet()}
	
	/*
		Go through all <include> tags on page. Initialise the included
		asset, and store its header to create a list of assets.
	*/
	
	var includes = document.getElementsByTagName('include'),
		header,
		includeList = [];
	for (var i=0;i<includes.length;i++) {
		header = createInclude(includes[i]).header;
		if (!header.id) {
			header.id = 'politics' + i;
		}
		includeList.push(header.innerHTML);
		includeList.push('#' + header.id);
	}
//	setChapterTitles(includeList);

	/* remove include tags; mainly to allow the div.wrapper + div.wrapper CSS rule */

	for (var i=0;i<includes.length;i++) {
		includes[i].parentNode.removeChild(includes[i]);
	}
	
//	setChapterTitles.writeroot.innerHTML += '<p>' + openFigures + ' figures opened.</p>'

	setPartyNames();
	
	/* In a separate function to create a closure for dataObj */
	
	function createInclude(data) {	
		/*
			create data object dataObj to be sent to 
			graph creation function.
			All attributes in the HTML are copied to
			this data object, and most of them are
			sent on to the graph creation function.
			Data used in this function:
				closed:		graph should be closed by default
				legend:		content of the graph header
				explanation:	Explanation text in graph

			NOTE: the copying is done through the attributes
			array, in which IE makes the names all lower case.
		*/

		var dataObj = {};
		var atts = data.attributes;
		for (var j=0;j<atts.length;j+=1) {
			if (atts[j].nodeValue) {
				dataObj[atts[j].name] = atts[j].value;
			}
		}

		if (!dataObj.datasrc) {
			dataObj.datasrc = parliament;
		} else {
			dataObj.datasrc = window[dataObj.datasrc];
		}

		/*
			Create wrapper and header (see below). Copy wrapper
			and header to dataObj. Insert wrapper at the
			place of the <include> tag in the HTML.
		*/

		var wrapper = createWrapper(dataObj);
		dataObj.wrapper = wrapper.wrapper;
		dataObj.header = wrapper.header;
		includes[i].parentNode.insertBefore(wrapper,includes[i]);

		/*
			Create the requested graph with the data in dataObj,
			but only if the graph is initially opened.
			This is done with a delay to give the other scripts
			a chance. The page *seems* to load faster with this
			trick in place.
			Needs to be in closure for dataObj.
		*/

		if (dataObj.open) {
			setTimeout(function () {createGraph(dataObj);},200);
			openFigures += 1;
		}

		/*
			Start include header with 'Graph', 'Map' or 'Table'
			If dataObj contains a legend, set the header to
			this legend; if not set it to whatever its
			header creation function returns.
		*/

		var header = '';
		switch (dataObj.type) {
			case "parliament":
			case "partySeats":
				if (dataObj.map) {
					header = '<span>Graph and map:</span> ';
				} else {
					header = '<span>Graph:</span> ';				
				}
				break;
			case "electionMap":
			case "partyMap":
				header = '<span>Map:</span> ';
				break;
			case "table":
				header = '<span>Table:</span> ';
		}

		header += (dataObj.legend || headers[dataObj.type](dataObj)) + ' ';
//		if (dataObj.type !== 'parliament') {
//			header += dataObj.startyear || standardstartyear;
//			header += ' - ' + (dataObj.endyear || dataObj.datasrc.order[dataObj.datasrc.order.length-1]);
//		} 

		dataObj.header.innerHTML = header;
		
		return dataObj;
		
		/*
		
			GRAPH CREATION
			
			If there's an extra form, generate that form and sumbit it.
			If not, create graph directly.
		
		*/
		
		function createGraph(data) {
			if (data.form) {
				create.form(data);
			} else {		
				create[data.type](data);
			}
		}

		/*
			Creates a wrapper for an <include>.
			Create an outer wrapper (always visible), a header
			and an inner wrapper (may be hidden by close link)
			Create a open/close link and make sure it
			shows/hides the inner wrapper.

			If the graph is initially closed, the first 'Open'
			click also creates the graph. This matters in
			pages with lots of graphs.

			Append closelink, header and innerwrapper to
			outerwrapper. 
		*/

		function createWrapper(data) {
			var outerWrapper = document.createElement('div');
			outerWrapper.className = 'wrapper';
			
			var message = document.createElement('p');
			message.innerHTML = 'Generating ... please wait';

			var header = document.createElement('h5');

			var innerWrapper = document.createElement('div');
			innerWrapper.className = 'innerwrapper';

			var openClose = document.createElement('a');
			openClose.className = 'openClose';
			openClose.innerHTML = 'Close';
			openClose.href = '#';

			var status = false;
			openClose.onclick = function () {
				if (status) {
					if (!data.open) {
						innerWrapper.appendChild(message);
						setTimeout(function () {
							createGraph(dataObj);
							innerWrapper.removeChild(message);
						},100);
						data.open = true;
					}
					innerWrapper.style.display = 'block';
					this.innerHTML = 'Close';
					outerWrapper.className = 'wrapper';
				} else {
					innerWrapper.style.display = 'none';
					this.innerHTML = 'Open';
					outerWrapper.className += ' closed';
				}
				status = !status;
				return false;
			}
			if (!data.open) {
				openClose.onclick();
			}

			outerWrapper.appendChild(openClose);
			outerWrapper.appendChild(header);

			if (data.explanation) {
				var caption = document.createElement('p');
				caption.className = 'graphCaption'
				var text = dataObj.explanation.replace(/\[/g,'<');
				text = text.replace(/\]/g,'>');
				caption.innerHTML = text;
				outerWrapper.appendChild(caption);
				delete data.explanation;
			}

			header.onclick = function(){openClose.onclick()};

			outerWrapper.appendChild(innerWrapper);

			outerWrapper.wrapper = innerWrapper;
			outerWrapper.header = header;
			return outerWrapper;
		}
	}	
	
	function setPartyNames() {
		var spans = document.getElementsByTagName('span');
		for (var i=0,span;span=spans[i];i+=1) {
			if (span.className === 'partyName') {
				span.style.color = partyData[span.innerHTML].color || defaultColor;
			}
		}
	}
}


var headers = {
	parliament: function (data) {
		var text = data.year + ' elections ';
		var province = data.province || 'national';
		if (data.datasrc[data.year]) {
			var dataSrc = data.datasrc[data.year];
			var emptySeats = dataSrc.totalSeats - (dataSrc.number[province] * dataSrc.medians[province]);
			text += ' <small>('
			     + dataSrc.number[province] + ' parties'
//			     + ' number = ' + dataSrc.number[province] 
//			     + ' median = ' + dataSrc.medians[province]
//			     + ' aboveMedian = ' + parseInt(dataSrc.aboveMedian[province]/dataSrc.totalSeats*100) + '%'	
//			     + ' seats = ' + dataSrc.totalSeats
//			     + ' smallestMaj = ' + dataSrc.smallestMaj[province]
//			     + '; unity ' + parseInt((parseInt(dataSrc.aboveMedian[province]/dataSrc.totalSeats*100)/(dataSrc.smallestMaj[province]))) + '%'
			     + ')</small>';
		}
		return text;
	},

	partySeats: function (data) {
		var text = this.readParties(data.parties) + ' seats ';
		return text;
	},

	partyMap: function (data) {
		return this.readParties(data.parties) + " regional support "; // + startyear + '-' + endyear;
	},

	electionMap: function (data) {
		return 'Election map '; // for ' + data.year;
	},
	
	table: function(data) {
		var startyear = data.startyear || data.datasrc.order[0];
		var endyear = data.endyear || data.datasrc.order[data.datasrc.order.length-1];
		var header = 'Parliaments '; // + startyear + ' - ' + endyear;
		if (data.province) {
			header += ' (' + data.province + ')';
		}
		return header;
	},
	readParties: function (partyNames) {
		if (!partyNames) return;
		var replacements = [
			{
				orig: 'Merged:',
				target: 'Predecessors of '
			},
			{
				orig: 'Denomination:',
				target: ''
			},
			{
				orig: 'Block:(.*)\+?',
				target: '$1 block'
			},
			{
				orig: 'Economics:',
				target: 'Economically '
			},
			{
				orig: 'Ethics:',
				target: 'Ethically '
			},
			{
				orig: '!',
				target: 'excluding '
			},
		]
		
		for (var i=0;i<replacements.length;i+=1) {
			var exp = new RegExp(replacements[i].orig,"g");
			partyNames = partyNames.replace(exp,replacements[i].target); 
		}
		return partyNames;
	}
}


var create = {

	/*
	
		FORM 
		
		Occurs only in poll page, and the createForm() function is defined there.
		
	*/
	
	form: function (data) {
		create.form = createForm;
		create.form(data);
	},


	/* PARLIAMENT GRAPH 
	
		data can have the following members:
			wrapper:	the HTML element the parliament is appended to
			header: 	the header of that HTML element
			year: 		the year we want the parliament from
			provinces:	show provincial data
			map:		add election map
			clickthrough:	the 'block' value cancels the onclick factsheet creation
	*/

	parliament: function (data) {
		
		var scale = 18;
		var drawGraph = [
			function (province,partyList) {
				var parliamentGraph = document.createElement('div'),
				  totalSeats = data.datasrc[data.year].totalSeats,
				  seatsInRow=data.seatsInRow || 25,
				  size=totalSeats/seatsInRow,
				  template = document.createElement('div'),
				  top=0,
				  left=0,
				  counter=0;
				parliamentGraph.className = 'parliamentGraph';
				template.className = 'seat';
				document.body.appendChild(parliamentGraph);
				parliamentGraph.style.height = size*scale + 'px';
				for (var i=0;i<seatsInRow;i+=1) {
					for (var j=0;j<size;j+=1) {
						var seat = template.cloneNode(true);
						seat.style.top = top + 'px';
						seat.style.left = left + 'px';
						top += scale;
						parliamentGraph.appendChild(seat);
					}
					scale = -scale;
					top += scale;
					left += Math.abs(scale);
				}
				scale = Math.abs(scale);
				if (!partyList) {
					partyList = partyOrder;
				}
				var allSeats = parliamentGraph.getElementsByTagName('div');
				var targetIndex = 0;
				var seatsOneParty = {},
				  bars = {	'Catholic': {
							totalSeats: 0,
							parties: [],
							seats: []
						},'Christian': {
							totalSeats: 0,
							parties: [],
							seats: []
						},'Protestant':{
							totalSeats: 0,
							parties: [],
							seats: []
						}
				};
				var firstFound = false;
				for (var i=0,party;party = partyList[i];i++) {
					var seatsParl = data.datasrc[data.year].seats[province][party]*1;
					if (seatsParl) {
						var denomination = partyData[party] && partyData[party].type.Denomination;
						if ( denomination === 'Catholic'
						    || denomination === 'Protestant'
						    || denomination === 'Christian') {
//						    	if (denomination === 'Christian') {
//						    		denomination = 'Catholic';
//						    	}
							bars[denomination].totalSeats += seatsParl;
							bars[denomination].parties.push(party);
							if (!firstFound) {
								firstFound = targetIndex;
							}
							targetIndex += seatsParl;
							
							
						} else {
							for (var j=0;j<seatsParl;j+=1) {
								setOneSeat(allSeats[targetIndex],party);
								targetIndex +=1;
							}
						}
					}
				}

				var rows = [];
				for (var i=firstFound,seat;seat=allSeats[i],!allSeats[i].party;i+=1) {
					var yCoor = parseInt(seat.style.top)/scale;
					if (!rows[yCoor]) {
						rows[yCoor] = [];
					}
					rows[yCoor].push(seat);
				}
				var bottom,top;
				if (data.year < 1977) {
					bottom = 'Catholic';
					top = 'Protestant';
				} else {
					bottom = 'Protestant';
					top = 'Christian';
				}
				
				var toBeSeated = bars[bottom].totalSeats;
				for (var i=rows.length-1,currentRow;currentRow=rows[i];i-=1) {
					if (toBeSeated >= currentRow.length) {
						for (var j=0,seat;seat=rows[i][j];j+=1) {
							seat.party = 'taken';
						}
						toBeSeated -= currentRow.length;
					} else {
						var protsLeft = Math.ceil((currentRow.length - toBeSeated)/2);
						for (var j=0,seat;seat=rows[i][j+protsLeft],j<toBeSeated;j+=1) {
							seat.party = 'taken';
						}
						toBeSeated = 0;
						break;
					}
				}
				for (var i=0,seat;seat=allSeats[i];i+=1) {
					if (seat.party === 'taken') {
						bars[bottom].seats.push(seat);
					} else if (seat.party) {
						continue;
					} else {
						bars[top].seats.push(seat);
					}
				}
				for (var i in bars) {
					var seats = bars[i].seats;
					var parties = bars[i].parties;
					for (var j=0,party;party=parties[j];j+=1) {
						var numberOfSeats = data.datasrc[data.year].seats[province][party]*1;
						for (var k=0;k<numberOfSeats;k+=1) {
							setOneSeat(seats.shift(),party);
						}
					}
				}


				function setOneSeat(seat,party) {
					seat.style.backgroundColor = getPartyColor(party);
					seat.text = party + ' ' + (seatsParl || numberOfSeats);
					seat.party = party;
					if (!seatsOneParty[party]) {
						seatsOneParty[party] = [];
					}
					seatsOneParty[party].push(seat);				
				}

				var mouseOver = document.createElement('div');
				mouseOver.className = 'mouseover';
				var currentParty;
				parliamentGraph.onmouseover = function (e) {
					var evt = e || window.event;
					var tgt = evt.target || evt.srcElement;
					if (tgt.className === 'seat' && currentParty !== tgt.party) {
						var first = seatsOneParty[tgt.party][0];
						mouseOver.innerHTML = tgt.text;
						mouseOver.style.left = first.style.left;
						mouseOver.style.backgroundColor = tgt.style.backgroundColor;
						parliamentGraph.appendChild(mouseOver);
						if (currentParty) {
							hilite(currentParty,'seat');
						}
						currentParty = tgt.party;
						hilite(tgt.party,'seat highlight');
					}
				}
				parliamentGraph.onmouseout = function (e) {
					var evt = e || window.event;
					var tgt = evt.relatedTarget || evt.toElement;
					if (!parliamentGraph.contains(tgt)) {
						hilite(currentParty,'seat');
						parliamentGraph.removeChild(mouseOver);
						currentParty = undefined;
					}
				}


				parliamentGraph.highlightParties = function () {
					this.className = 'highlightedGovernment';
					var govtParties = this.data.parties.split('+');
					for (var i=0;i<govtParties.length;i+=1) {
						hilite(govtParties[i],'seat highlight');
					}
				}

				parliamentGraph.unhighlight = function () {
					this.className = '';
					var govtParties = this.data.parties.split('+');
					for (var i=0;i<govtParties.length;i+=1) {
						hilite(govtParties[i],'seat');
					}			
				}

				parliamentGraph.onclick = create.showFactSheet;
				return parliamentGraph;

				function hilite(party,className) {
					for (var i=0,seat;seat=seatsOneParty[party][i];i+=1) {
						seat.className = className;
					}					
				}
			},
			function (province,partyList) {
				var partyBlocks = {};
				var parliamentSize = data.totalWidth || 450; 
				var seatSize = parliamentSize / data.datasrc[data.year].totalSeats;
				var parliamentGraph = document.createElement('div');
				data.wrapper.onmouseover = showToolTip;
				document.body.appendChild(parliamentGraph);
				if (!partyList) {
					partyList = partyOrder;
				}
				parliamentGraph.showyear = data.year;
				parliamentGraph.className = 'parliament';
				if (data.clickthrough !== 'blocked') {
					parliamentGraph.onclick = create.showFactSheet;
				}
				var counter = 0;
				var totalWidth = 7;
				for (var i=0,party;party = partyList[i];i++) {
					var seatsParl = data.datasrc[data.year].seats[province][party]*1;
					if (!seatsParl) continue;

					var partyWidth = Math.round(seatsParl*seatSize);

					var el = document.createElement('div');
					if (province === 'national')
						partyBlocks[party] = el;
					el.style.left = totalWidth + 'px';
					el.style.width = partyWidth + 'px';
					el.party = party;

					totalWidth += partyWidth;
					var color = getPartyColor(party);
					el.style.backgroundColor = color;
					
					parliamentGraph.appendChild(el);
					var textObj = {};
					textObj.party = party;
					textObj.seats = seatsParl;
					textObj.element = el;
					create.setText(textObj);
				}

				parliamentGraph.highlightParties = function () {
					this.className = 'highlightedGovernment';
					var govtParties = this.data.parties.split('+');
					for (var i=0;i<govtParties.length;i+=1) {
						partyBlocks[govtParties[i]].className = 'governmentParty';
					}
				}

				parliamentGraph.unhighlight = function() {
					this.className = '';
					var govtParties = this.data.parties.split('+');
					for (var i=0;i<govtParties.length;i+=1) {
						partyBlocks[govtParties[i]].className = '';
					}		
				}

				return parliamentGraph;
			}
		];

		var colorCounter = 0;
		function getPartyColor(party) {
			var colors = ['#333333','#666666','#999999'],
				color;
			if (partyData[party] && partyData[party].color) {
				color = partyData[party].color;
			} else {
				color = colors[colorCounter % colors.length];
				colorCounter+=1;
				if (partyData[party]) {
					partyData[party].color = color;
				}
			}
			return color;
		}

		if (data.year === 'Last') {
			data.year = data.datasrc.order[data.datasrc.order.length - 1];
//			data.header.innerHTML = headers.parliament(data);
		}
		
		if (!data.datasrc[data.year]) return;
		
		var currentGraph = (Cookies.parliamentGraph || 2)*1;
		function createGraph(province,partyList) {
			var type = (Cookies.parliamentGraph || 2) - 1;
			return drawGraph[type](province,partyList);
		}
		function highlightParties() {
			parliamentGraph.highlightParties.call(this);
		}
		function unhighlight() {
			parliamentGraph.unhighlight.call(this);
		}
		
		/*
		var link = document.createElement('a');
		link.innerHTML = 'Change graph';
		link.href = '#';
		link.className = 'open';
		link.onclick = function () {
			var newGraph = (currentGraph === 1) ? 2 : 1;
			Cookies.create('parliamentGraph',newGraph,365);
			data.wrapper.removeChild(parliamentGraph);
			parliamentGraph = createGraph(target);
			data.wrapper.insertBefore(parliamentGraph,link.nextSibling);
			currentGraph = newGraph;
			return false;
		}
		data.wrapper.appendChild(link);
		*/
		
		var target = data.province || 'national';
				
		var parliamentGraph = createGraph(target);
		data.wrapper.appendChild(parliamentGraph);

		var govts = data.datasrc[data.year].government;
		if (!data.province && govts) {
			var embedObj = {};
			embedObj.dataObject = data;
			embedObj.text = 'government';
			embedObj.wrapper = data.wrapper;
			embedObj.callback = function (data) {
				var list = document.createElement('ol');
				list.className = 'governments';
				for (var i=0;i<govts.length;i++) {
					var item = document.createElement('li');
					item.data = govts[i];
					item.onmouseover = highlightParties;
					item.onmouseout = unhighlight;
					var span = document.createElement('span');
					span.appendChild(document.createTextNode(govts[i].name));
					span.style.color = (partyData[item.data.party] && partyData[item.data.party].color) || '#333333';
					item.appendChild(span);
					item.appendChild(document.createTextNode(' (' + govts[i].start + ' - ' + govts[i].end + ')'));
					if (govts[i].cabinet) {
						var cabinet = createInfoBlock(govts[i]);
						item.appendChild(cabinet);
					}
					list.appendChild(item);
				}
				data.wrapper.appendChild(list);
			}
			this.embed(embedObj);
		} 

		
		
		if (data.final && data.datasrc[data.year].dissidents) {
			var embedObj = {};
			embedObj.wrapper = data.wrapper;
			embedObj.text = 'final composition';
			embedObj.dataObject = data;
			embedObj.callback = function (data) {
				var dissidents = data.datasrc[data.year].dissidents;
				var toCopy = data.datasrc[data.year].seats.national;
				var result = {};
				for (var i in toCopy){
					result[i] = toCopy[i];
				}

				for (var i=0,dissident;dissident=dissidents[i];i+=1) {
					var fromParty = dissident.from;
					var toParty = dissident.to;
					var number = dissident.number || 1;
					result[fromParty] -= number;
					if (!result[toParty]) {
						result[toParty] = 0;
					}
					result[toParty] += number;
				} 
				
				/* Put parties in normal left-to-right order */
				
				var inPartyOrder = [],
					pointers={};
				for (var i=0,party;party=partyOrder[i];i+=1) {
					if (result[party] !== undefined) {
						inPartyOrder.push(party);
					}
				}
				setPointers(inPartyOrder);

				/* For failed parties that don't appear in the
				   main party data array. 
				   Create temporary party right of the
				   mother party. */

				for (var i=0,dissident;dissident=dissidents[i];i+=1) {
					var toParty = dissident.to;
					if (!partyData[toParty]) {
						var fromParty = dissident.from;
						inPartyOrder.splice(pointers[fromParty]+1,0,toParty);
						setPointers(inPartyOrder);
					}
				}

				/* Create temporary province 'dissidents' for graph creation */

				data.datasrc[data.year].seats.dissidents = result;
				var endResult = createGraph('dissidents',inPartyOrder);
				delete data.datasrc[data.year].seats.dissidents;

				var dissidentParliament = document.createElement('div');
				dissidentParliament.appendChild(endResult);
				data.wrapper.appendChild(dissidentParliament);

				function setPointers(array) {
					for (var i=0,el;el=array[i];i+=1) {
						pointers[el] = i;
					}
				}
			}
			this.embed(embedObj);
		}

		if (data.provinces && data.datasrc[data.year].seats['Groningen']) {
			var embedObj = {};
			embedObj.wrapper = data.wrapper;
			embedObj.text = 'provinces';
			embedObj.dataObject = data;
			embedObj.callback = function (data) {

				var provinceHolder = document.createElement('div');
				data.wrapper.appendChild(provinceHolder);

				for (var i in data.datasrc[data.year].seats) {
					if (i !== 'national') {
						createProvinceLink(i);
					}
				}

				var removeFunction = function () {};
				
				function createProvinceLink(province) {
					var link = document.createElement('a');
					link.href = '#';
					link.className = 'open';
					link.innerHTML = province;
					provinceHolder.appendChild(link);
					provinceHolder.appendChild(document.createTextNode(' '));
					var subGraph;
					link.onclick = function () {
						removeFunction();
						this.className += ' opened';

						if (!subGraph) {
							subGraph = createGraph(province);
							provinceHolder.insertBefore(subGraph,provinceHolder.firstChild);
						}
						else {
							subGraph.style.display = 'block';
						}

						removeFunction = function () {
							subGraph.style.display = 'none';
							link.className = 'open';
						}

						return false;
					};				
				}

			}
			this.embed(embedObj);
		}

		if (data.map && data.datasrc[data.year].seats['Groningen']) {
			var mapData = {};
			mapData.wrapper = data.wrapper;
			mapData.callback = create.electionMap;
			mapData.text = 'map';
			mapData.dataObject = {};
			mapData.dataObject.year = data.year;
			this.embed(mapData);
		}


		function createInfoBlock(data) {
			var block = document.createElement('div');
			var left = 0,
				primeMinister = false;
			var ministers = data.cabinet;
			var sortArray = [];
			for (var i in ministers) {
				var newItem = {};
				newItem.name = i;
				newItem.seats = ministers[i];
				sortArray[sortArray.length] = newItem;
			}
			
			sortArray.sort(function (a,b) {
//				if (data.party === a.name) {
//					return -1;
//				} else {
					return b.seats - a.seats;
//				}
			});
			
			for (var i=0,party;party=sortArray[i];i+=1) {
				createSingleBlock(party.name);
			}
			return block;
			
			function createSingleBlock(name) {
				var color = (partyData[name] && partyData[name].color) || '#666666';
				for (var i=0;i<ministers[name];i+=1) {
					var singleBlock = document.createElement('div');
					singleBlock.className = 'seat';
					singleBlock.style.left = left + 'px';
					left += Math.abs(scale);
					singleBlock.style.backgroundColor = color;
					if (!primeMinister && data.party === name) {
						singleBlock.style.height = '20px';
						primeMinister = true;
					}
					block.appendChild(singleBlock);
				}
			}
		}
		
	},
	
	/* 
		SEATS GRAPH 
	
		data can have the following members:
			wrapper:	the HTML element the parliament is appended to
			header: 	the header of that HTML element
			parties:	the parties shown in the graph
			startyear:	an optional start year (default: global standardstartyear)
			endyear:	an optional end year (default: last elections)
			scale:		pixels per seat; default global standardScale
			government	show arrow if party participates in government
	*/
	
	partySeats: function (data) {
		var seatsGraph = document.createElement('div');
		seatsGraph.className = 'seatsGraph';
		seatsGraph.onmouseover = showToolTip;
		seatsGraph.onclick = create.showFactSheet;
 		data.wrapper.appendChild(seatsGraph);

		var parties = this.createPartyList(data.parties);
		var province = data.province || 'national';
/*		var startyear = data.startyear || standardstartyear;
		var endyear = data.endyear || standardendyear; */
		
		var years = this.getAllYears(data);
		
		var scale = data.scale || standardScale;
		var left = 0,
			bottom = 0,
			tallestColumn = 0;
		var governments = [];
		var partiesInMap = {};

		for (var k=0,i;i=years[k];k+=1) {
			if (data.government) {
				governments.length = 0;
				if (data.datasrc[i].government) {
					var govData = data.datasrc[i].government;
					for (var l=0;l<govData.length;l+=1) {
						governments.push(govData[l].parties);
					}
				}
			}		

			
			var yearWrapper = document.createElement('div');
			yearWrapper.className = 'yearWrapper';
			yearWrapper.showyear = i;
			yearWrapper.style.left = left + 'px';
			var width = 4 + 12*data.datasrc[i].length;
			yearWrapper.style.width =  width + 'px';
			seatsGraph.appendChild(yearWrapper);
			var year = document.createElement('div');
			year.className = 'year';
			year.style.bottom = 0;
			year.innerHTML = i;
			yearWrapper.appendChild(year);
			bottom += year.offsetHeight;
			var totalSeats = 0;
			var counter = 0;
			var inGovernment = {};
			
			if (data.size) {
//				parties = [data.datasrc[i].partyOrder[province][data.size-1]];
				parties = data.datasrc[i].partyOrder[province].slice(0,data.size).reverse();
			}
			
			for (var j=0;j<parties.length;j++) {
				var recordExists = data.datasrc[i].seats[province];
				if (!recordExists) continue;
				var seatsYear = recordExists[parties[j]] 
					|| (data.datasrc[i][parties[j]] && data.datasrc[i][parties[j]][province]) 
					|| 0;
				if (!seatsYear) continue;
				partiesInMap[parties[j]] = true;
				totalSeats += seatsYear;
				var partyColumn = document.createElement('div');
				partyColumn.className = 'party';
				partyColumn.party = parties[j];
				var partyHeight = Math.round(seatsYear*scale * (150/data.datasrc[i].totalSeats));
				partyColumn.style.height = partyHeight + 'px';
				partyColumn.style.bottom = bottom + 'px';
				yearWrapper.appendChild(partyColumn);
				if (parties.length > 1 || data.size) {
					var textObj = {};
					textObj.party = parties[j];
					textObj.seats = seatsYear;
					textObj.element = partyColumn;
					create.setText(textObj);
				}
				bottom += partyColumn.offsetHeight;
				var partyColor = partyData[parties[j]] && partyData[parties[j]].color;
				if (partyColor) {
					partyColumn.style.backgroundColor = partyColor;
					counter = 0;
				}
				else {
					if (counter %2 == 1)
						partyColumn.className += ' even';
					counter++;
				}

				for (var l=0;l<governments.length;l+=1) {
					if (governments[l].indexOf(parties[j]) !== -1) {
						inGovernment[parties[j]] = true;
					}
				}
			}
			
			var total = document.createElement('div');
			total.className = 'total';
			total.innerHTML = totalSeats;
			total.style.bottom = bottom + 'px';
			yearWrapper.appendChild(total);
			bottom += total.offsetHeight;

			if (data.government) {
				var any = false;
				var all = true;
				for (var q in inGovernment) {
					any = true;	
				}
				for (var l=0;l<parties.length;l+=1) {
					if (data.datasrc[i].seats[province][parties[l]] && !inGovernment[parties[l]]) {
						all = false;
					}
				}
				var showGovernment;
				if (data.government === 'all') {
					showGovernment = all;
				} else {
					showGovernment = any;
				}

				if (showGovernment) {
					var govMarker = document.createElement('div');
					govMarker.className = 'govMarker';
					yearWrapper.appendChild(govMarker);
					bottom += 10;
				}
			}
			
			if (bottom > tallestColumn) {
				tallestColumn = bottom;
			}
			bottom = 0;
			left += yearWrapper.offsetWidth + 2;
		}
		seatsGraph.style.height = tallestColumn + 'px';

		if (years[0] < 1956 && years[years.length-1] >= 1956) {
			var text = document.createElement('p');
			text.className = 'smaller';
			text.innerHTML = sizeText;
			data.wrapper.appendChild(text);
		}
		if (data.map && data.datasrc[years[0]].seats['Groningen']) {
			var mapData = {};
			mapData.wrapper = data.wrapper;
			mapData.callback = create.partyMap;
			mapData.text = 'map';

			mapData.dataObject = {}
			for (var i in data) {
				mapData.dataObject[i] = data[i];
			}
			var shownString = '';
			for (var i in partiesInMap) {
				shownString += i + '+';
			}
			shownString = shownString.substring(0,shownString.length-1);
			mapData.dataObject.parties = shownString;
			mapData.dataObject.startyear = years[0];
			mapData.dataObject.endyear = years[years.length-1];
//			mapData.dataObject.factsheet = true;
			
			this.embed(mapData);
		}
	},

	/* PARTY MAP

		data can have the following members:
			wrapper:	the HTML element the parliament is appended to
			header: 	the header of that HTML element
			parties:	the parties shown in the select box
					(select only shown when 2 or more parties)
			startyear:	an optional start year (default: global standardstartyear)
			endyear:	an optional end year (default: last elections)
			startparty:	the party that's shown first
	
	*/
	
	partyMap: function (data) {
   		var mapExplanation = 'This map shows the seats a party would have won in each of the twelve provinces.';
		var startyear = data.startyear || data.datasrc.order[0];
		var endyear = data.endyear || data.datasrc.order[data.datasrc.order.length-1]; 
		var showYear;

		var expl = document.createElement('p');
		expl.className = 'smaller';
		expl.appendChild(document.createTextNode(mapExplanation));

		var map = this.createMap();
		var dataHolder = document.createElement('div');
		dataHolder.className = 'dataHolder';
		var subheader = document.createElement('h6');

		var parties = this.createPartyList(data.parties);

		var picker;
		if (parties.length > 1) {
			parties.sort();
			picker = document.createElement('select');
			for (var i=0,party;party=parties[i];i+=1) {
				if (partyData[party].elections) {
					var firstElection = partyData[party].elections[0];
					var lastElection = partyData[party].elections[partyData[party].elections.length-1];

					if (firstElection > endyear || lastElection < startyear) {
						continue;
					}
					picker.options[picker.options.length] = new Option(parties[i],parties[i]);
					if (parties[i] === data.startparty) {
						picker.options[picker.options.length-1].selected = true;
					}
				}
			}
			picker.onchange = function () {
				createMap(this.value);
			}
			data.wrapper.appendChild(picker);
		}
		
		data.wrapper.appendChild(map.element);
		data.wrapper.appendChild(expl);

		var doneCalculations = {};
		
		if (data.combined) {
			var combined = {};
			for (var i=0,party;party=parties[i];i+=1) {
				var totalSeats = doCalculations(party);
				for (var j in totalSeats) {
					if (!combined[j]) {
						combined[j] = {};
						combined[j].counter = 0;
					}
					for (var k in totalSeats[j]) {
						if (!combined[j][k]) {
							combined[j][k] = 0;
						}
						combined[j][k] += totalSeats[j][k];
						combined[j].counter +=1;
					}
				}
			}
			doneCalculations.combined = combined;
			picker.options[picker.options.length] = new Option('Combined','combined');
		}

		if (picker) {
			picker.onchange();
		}
		else {
			createMap(parties[0]);
		}

		function doCalculations(party) {
			if (doneCalculations[party]) {
				return doneCalculations[party];
			}
			var counters = {};
			var totalSeats = {};
			totalSeats.average = {};
			var totalElections = 0;
			for (var i=0,year;year=partyData[party].elections[i];i+=1) {
				if (!data.datasrc[year].seats.Groningen) continue;
				if (year >= startyear && year <= endyear) {
					totalElections += 1;
					var results = data.datasrc[year].seats;
					for (var province in results) {	
						var seatsInProvince = results[province][party] || 0;
						if (totalSeats.average[province] === undefined) {
							totalSeats.average[province] = 0;
							counters[province] = 0;
						}
						var modifiedSeats = seatsInProvince;
						if (data.endyear >= 1956 && year < 1956)
							modifiedSeats = Math.round(modifiedSeats * 1.5);
						totalSeats.average[province] += modifiedSeats;
						counters[province] += 1;
						if (!totalSeats[year]) {
							totalSeats[year] = {};
						}
						totalSeats[year][province] = seatsInProvince;
					}
				}
			}
			if (totalElections < 2) {
				delete totalSeats.average;
			}
			for (var i in totalSeats.average) {
				totalSeats.average[i] = Math.round(totalSeats.average[i]/counters[i] || 0);
			}
			doneCalculations[party] = totalSeats;
			return totalSeats;
		}
		
		function createMap(party) {
			emptyMap();
			emptyDataHolder();
			var totalSeats = doCalculations(party);

			if (!showYear) {				
				showYear = data.showyear;
			}
			
			/* If only single year showYear = that year */
			
			if (partyData[party] && !partyData[party].yearLookup[showYear]) {
				if (totalSeats.average) {
					showYear = 'average';
				} else { 	// totalSeats has only one member
					for (var i in totalSeats) {
						showYear = i;
					}
				}
			}
			
			var previous;
			for (var i in totalSeats) {
				var link = document.createElement('a');
				link.innerHTML = i;
				link.href = '#';
				dataHolder.appendChild(link);
				dataHolder.appendChild(document.createTextNode(' '));
				(function (i) {
					link.onclick = function () {
						if (previous) {
							previous.className = '';
						}
					// IE bug: subheader doesn't exist?
						subheader.innerHTML = '<strong>' + totalSeats[i].national + '</strong> seats nationally';
						emptyMap();
						createPartyMapElements(totalSeats[i]);
						this.className  += ' active';
						previous = this;
						showYear = i;
						return false;
					}
				})(i);
				if (i === showYear) {
					link.onclick();
				}
			}

			if (data.factsheet && partyData[party]) {
				var embedData = {};
				embedData.wrapper = dataHolder;
				embedData.callback = create.factSheet;
				embedData.dataObject = data;
				embedData.dataObject.parties = party;
				embedData.text = party + ' fact sheet';
				create.embed(embedData);
			}

			function createPartyMapElements(totalSeats) {
				for (var i in totalSeats) {
					if (i === 'national') continue;
					var difference = totalSeats[i] - totalSeats.national;
					var suffix;
					if (difference < 0 || totalSeats[i] === 0) {
						suffix = '_negatief';
					} else if (difference > 0) {
						suffix = '_positief';
					} else {
						suffix = '_neutraal';
					}

					// to get more colour spread when national seats is zero

					var nationalSeats = totalSeats.national;

					if (nationalSeats === 0) {
						 nationalSeats = 1;
					}

					var percentage = Math.abs((1 - totalSeats[i]/nationalSeats)*10);

					// to get more colour spread for very small parties
					// if not for this extra calculation, any province with more or less than the
					// national number of seats gets full opacity (bright red or green)

					var divider = Math.max(4-nationalSeats,1);
					percentage /= divider;

					var className = create.setOpacity(percentage);
					var provinceName = provinceToSystem(i);

					if (totalSeats[i] === 0) {
						className = '';
						suffix = '_none';
					}

					var img = document.createElement('img');
					map.element.appendChild(img);
					img.src = '/politics/pix/maps/map_' + provinceName + suffix + '.gif';
					img.className = provinceName + ' ' + className;
					if (map[provinceName]) {
						map[provinceName](totalSeats[i]);
					}
				}
			}
			

			/* 	
				Complicated: the map should be wiped clean before a new
				one is produced, but the dataHolder should be preserved.
				Therefore we re-append it after the wipe.
				The same goes for the dataHolder, but then the subheader
				should be preserved.
				IE bug: if dataHolder and subheader are not removed before the 
				innerHTML wipe, their data is lost 
			*/

			function emptyMap() {
				if (dataHolder.parentNode) 
					map.element.removeChild(dataHolder);
				map.element.innerHTML = '';
				map.element.appendChild(dataHolder);			
			}
			
			function emptyDataHolder() {
				if (subheader.parentNode) 
					dataHolder.removeChild(subheader);
				dataHolder.innerHTML = '';			
				dataHolder.appendChild(subheader);
			}
		}
	},
	
	/* ELECTION MAP */
	
	electionMap: function (data) {
		var map = this.createMap();
		var dataHolder = document.createElement('div');
		dataHolder.className = 'dataHolder';

		var radioCollection = [];

		createRadio(5);
		createRadio(10);
		createRadio(15);
		var max = data.datasrc[data.year].totalSeats/20;
		for (var i=2;i<=max;i+=1) {
			createRadio(i*10);
		}
		createRadio('win','Winners');
		createRadio('empty','Nothing');

		var year = data.year;
		var seatsData = data.datasrc[year].seats;
		var dataArrays = data.datasrc[year].partyOrder;

		data.wrapper.appendChild(map.element);
		map.element.appendChild(dataHolder);

		for (var i in dataArrays) {
			if (i === 'national') continue;
			var provinceName = provinceToSystem(i);
			var currentArray = dataArrays[i];
			
			var largestParty = currentArray[0];
			var secondParty = currentArray[1];
			var largestPartySeats = seatsData[i][largestParty];
			var secondPartySeats = seatsData[i][secondParty];
			var difference = largestPartySeats - secondPartySeats;
			var source,opacity;
			if (difference !== 0) {
				switch (largestParty) {
					case "SDAP":
						largestParty = 'PvdA';
						break;
					case "RKK":
					case "RKSP":
						largestParty = 'KVP';
				}
				source = '/politics/pix/maps/map_' + largestParty + '_' + provinceName + '.gif';
				opacity = Math.sqrt(difference)*1.4;
			} else {
				source = '/politics/pix/maps/map_' + provinceName + '_neutraal.gif';
				opacity = 3;
			}
			var img = document.createElement('img');
			img.src = source;
			map.element.appendChild(img);
			var className = this.setOpacity(opacity);
			img.className = provinceName + ' ' + className;
		}
	
		if (data.links) {
			for (var i=0;i<data.datasrc.order.length;i+=1) {
				if (data.datasrc.order[i] === year) break;
			}

			if (i > 0) {
				createLink(data.datasrc.order[i-1]);
			}
			if (i < data.datasrc.order.length-1) {
				createLink(data.datasrc.order[i+1]);
			}
		}
	
		if (data.radioChecked !== undefined) {
			radioCollection[data.radioChecked].checked = true;
			radioCollection[data.radioChecked].onclick();
		}
		
		function createRadio(i) {
			var radio = document.createElement('input');
			radio.type = 'radio';
			radio.name = 'border';
			radio.data = radioCollection.length; 
				// length at the moment it's created; equal to index number
			var label = document.createElement('label');
			label.appendChild(radio);
			label.appendChild(document.createTextNode(i));
			dataHolder.appendChild(label);
			radio.onclick = function () {
				data.radioChecked = this.data;
				if (i === 'win') {
					addWinner();
				}
				else if (i === 'empty') {
					addSeatData(200);
				}
				else if (typeof i === 'number') {
					addSeatData(i);
				}
			}
			radioCollection.push(radio);
		}

		function createLink(year) {
			var link = document.createElement('a');
			link.href = "#";
			link.innerHTML = year;
			link.onclick = function () {
				data.year = year;
				data.wrapper.innerHTML = '';
				create.electionMap(data);
				return false;
			}
			dataHolder.appendChild(link);
		}
	
		function addSeatData(border) {
			for (var i in dataArrays) {
				if (i === 'national') continue;
				var currentArray = dataArrays[i];
				var frag = document.createDocumentFragment();
				var total = 0;
				for (var j=0;j<currentArray.length;j+=1) {
					var seats = seatsData[i][currentArray[j]];
					if (seats < border) break;
					total += seats;
					var sp = document.createElement('span');
					sp.innerHTML = currentArray[j] + ' ' + seats;
					sp.style.backgroundColor = partyData[currentArray[j]].color || defaultColor;
					frag.appendChild(sp);
				}
				if (map[provinceToSystem(i)]) {
					map[provinceToSystem(i)](frag);
				}
			}
		}
		
		function addWinner() {
			for (var i in dataArrays) {
				if (i === 'national') continue;
				var currentArray = dataArrays[i];
				var frag = document.createDocumentFragment();
				var largestParty = currentArray[0];
				var secondParty = currentArray[1];
				var largestPartySeats = seatsData[i][largestParty];
				var secondPartySeats = seatsData[i][secondParty];
				var difference = largestPartySeats - secondPartySeats;
				var sp = document.createElement('span');
				if (difference === 0) {
					sp.appendChild(document.createTextNode(largestParty+'/'+secondParty));
					sp.style.color = '#000000';
				}
				else {
					sp.appendChild(document.createTextNode(largestParty + ' +' + difference));
					sp.style.backgroundColor = partyData[largestParty].color;
				}
				frag.appendChild(sp);
				map[provinceToSystem(i)](frag);
			}		
		}
	},
	
	
	/* 
		GENERIC MAP
		Create a map object. It contains:
			element: the HTML element that is the map
			methods: one for each province 
				meant for easy access to the spans that hold
				province data
				i.e. map.groningen(text) writes text in the
				span centered on Groningen
				After writing, the span is re-centered
	*/
	
	createMap: function () {
		var provinces = ['groningen','friesland','drenthe','overijssel','gelderland','flevoland'
			,'utrecht','noordholland','zuidholland','zeeland','noordbrabant','limburg'];
		var map = {};
		var mapHolder = document.createElement('div');
		mapHolder.className = 'map';
		map.element = mapHolder;
		for (var i=0;i<provinces.length;i+=1) {
			map[provinces[i]] = textElement(provinces[i]);
		}
		return map;
		
		function textElement(name) {
			var el = document.createElement('span');
			el.className = name;
			return function (data) {
				el.innerHTML = '';
				if (typeof data === 'object') {
					el.appendChild(data);
				}
				else {
					el.innerHTML = data;
				}
				mapHolder.appendChild(el);
				el.style.marginLeft = -(el.offsetWidth/2) + 'px';
				el.style.marginTop = -(el.offsetHeight/2) + 'px';
			}		
		}
	},
	
	/* TABLE */
	
	table: function(data) {

		if (!data.province) {
			data.province = 'national';
		}
		
		var years = this.getAllYears(data);
		
		var initialYear = data.year || years[years.length-1];
		var currentCriterion = data.criterion || 'Seats';
		
		var linkHolder = document.createElement('div');
		var previousLink;
		for (var i=0;i<years.length;i+=1) {
			var linkYear = years[i];
			var link = document.createElement('a');
			link.href = '#';
			link.innerHTML = linkYear;
			link.onclick = function () {
				if (previousLink) {
					previousLink.className = '';
				}
				this.className = 'dataLink';
				linkYear = this.innerHTML
				prepareTable(linkYear);
				previousLink = this;
				return false;
			}
			linkHolder.appendChild(link);
			linkHolder.appendChild(document.createTextNode(' '));
			if (linkYear === initialYear) {
				link.className = 'dataLink';
				previousLink = link;
			}
		}

		data.wrapper.appendChild(linkHolder);

		var sortableOn = '' + data.sortable + ',Party,Seats,Gain';

		createTable();

		var currentElection,
		previousElection,	// data for current and previous elections
		rows = {},		// to store created party rows for later use
		root,			// the tbody the rows are appended to
		extraTDs = [],		// for combined seats and gains, should be removed
		changedTDs = [],	// for tds that are hidden or stretched, should be unhidden and unstretched
		partiesInOrder = [],	// party objects smallest-first-sorted by seat number
		year;			// current year

		prepareTable(initialYear);

		function prepareTable(currentYear) {
			year = currentYear;
			for (var i in rows) {
				rows[i].parentNode.removeChild(rows[i]);
				delete rows[i];
			}
			partiesInOrder = data.datasrc[year].totalLists[data.province];
			extraTDs.length = changedTDs.length = 0;
			createView(currentCriterion);
		}
		

		function createView(criterion) {
			while (extraTDs.length) {
				extraTDs[0].parentNode.removeChild(extraTDs.shift());
			}
			while (changedTDs.length) {
				changedTDs[0].style.display = '';
				changedTDs[0].className = '';
				if (changedTDs[0].original) {
					changedTDs[0].innerHTML = addPlus(changedTDs[0].original);
				}
				changedTDs.shift().rowSpan = 1;
			}
			
			var sorted = sortParties(criterion);

			for (var i=0,currentGroup;currentGroup = sorted[i];i+=1) {
				if (currentGroup.length === 0) continue;
				var row,
					previous,
					criterionTD,
					combinedSeats=0,
					combinedGains=0,
					combinedGain = {},
					mergers = {};

				for (var j=0,party;party=currentGroup[j];j+=1) {
					row = createRow(party);
					row.className = row.className.replace(/separator/,'');
					if (criterion !== 'Seats' && criterion !== 'Gain') {
						criterionTD = row[criterion];
						if (criterionTD) {
							criterionTD.style.display = 'none';
							changedTDs.push(criterionTD);
						}
					}
					if (party.merged && !party.Seats) {
						if (!mergers[party.merged]) {
							mergers[party.merged] = [];
							combinedGain[party.merged] = 0;
						}
						mergers[party.merged].push(row.Gain);
						combinedGain[party.merged] += row.Gain.original;
					}
					if (mergers[party.Party]) {
						row.Gain.rowSpan = mergers[party.Party].length + 1;
						changedTDs.push(row.Gain);
						row.Gain.innerHTML = addPlus(combinedGain[party.Party] + row.Gain.original);
						while (mergers[party.Party].length) {
							var td = mergers[party.Party].shift();
							td.style.display = 'none';
							changedTDs.push(td);
						}
					}
					combinedSeats += party.Seats;
					combinedGains += party.Gain;
					if (previous) {
						root.insertBefore(row,previous);
					}
					else {
						root.appendChild(row);
					}
					previous = row;
				}
				row.className += ' separator';
				if (criterion !== 'Gain' && row.Seats.innerHTML === '-') {
					row[currentCriterion].className += ' noseats';
					changedTDs.push(row[currentCriterion]);
				}
				if (criterionTD) {
					criterionTD.style.display = '';
					criterionTD.rowSpan = currentGroup.length;
				}
				if (criterion !== 'Seats') {
					addCombined(row);
				}
			}
			
			row.className = '';
			
			function addCombined(row) {
				var combSeats = document.createElement('td');
				combSeats.innerHTML = combinedSeats || '-';
				var majority = data.datasrc[year].totalSeats/2;
				if (combinedSeats > majority) {
					combSeats.className = 'majority';
					if (criterionTD) {
						criterionTD.className = 'majority';
					}
				}
				combSeats.rowSpan = currentGroup.length;
				var combGain = document.createElement('td');
				combGain.innerHTML = addPlus(combinedGains);
				combGain.rowSpan = currentGroup.length;
				row.appendChild(combSeats);
				row.appendChild(combGain);
				extraTDs.push(combSeats);
				extraTDs.push(combGain);
			}

		}
		
		function createRow(party) {
			if (!rows[party.Party]) {
				var tr = document.createElement('tr');
				tr.id = party.Party;
				var sortCriteria = sortableOn.split(',');
				var zeroSeats = false;
				for (var i=0;i<sortCriteria.length;i+=1) {
					if (sortCriteria[i] === 'undefined') continue;
					var data = '-';
					if (party[sortCriteria[i]] !== undefined) { // Seats and Gain
						data = party[sortCriteria[i]];
					} else if (partyData[party.Party].type[sortCriteria[i]]) {
						data = partyData[party.Party].type[sortCriteria[i]];
					} 

					var td = document.createElement('td');
					if (sortCriteria[i] === 'Gain') {
						td.original = data;
						data = addPlus(data);
					} else if (sortCriteria[i] === 'Govt') {
						var value = 'Out';
						var govts = getGovernment();
						if (govts) {
							for (var j=0;j<govts.length;j+=1) {
								if (party.Party === govts[j]) {
									value = 'In';
									break;
								}
							}
						}
						data = value;
					}
					if (sortCriteria[i] === 'Seats' && data === 0) {
						data = '-';
						zeroSeats = true;
					}
					td.innerHTML = data;
					tr[sortCriteria[i]] = td;
					tr.appendChild(td);
				}
				tr.Party.className = 'partyName';			

				var seats = tr.Seats.innerHTML*1;
				var gain = tr.Gain.original;
				var isNewParty = (seats === gain);

				if (zeroSeats) {
					tr.className += ' noseats';
					tr.Party.className += ' noseats';
				} else if (isNewParty) {
					tr.Party.style.backgroundColor = partyData[party.Party].color || defaultColor;
				} else {
					tr.Party.style.color = partyData[party.Party].color || defaultColor;
				}
				rows[party.Party] = tr;
			}
			return rows[party.Party];
		}

		function getGovernment() {
			if (!getGovernment[year]) {
				var value;
				if (data.datasrc[year].government) {
					value = data.datasrc[year].government[0].parties.split('+');
				}
				getGovernment[year] = value;
			}
			return getGovernment[year];
		}
	
		function sortParties(criterion) {
			var sortedParties = {};
			var seats = {},gain = {};
			switch (criterion) {
				case 'Seats':
					sortedParties.general = [];
					for (var i=0,party;party=partiesInOrder[i];i+=1) {
						sortedParties.general.push(party);
					}
					break;
				case 'Gain':
					sortedParties.Gain = [];	
					sortedParties.Equal = [];	
					sortedParties.Loss = [];
					for (var i=0,party;party=partiesInOrder[i];i+=1) {
						if (party.Gain > 0) {
							sortedParties.Gain.push(party);
						}
						else if (party.Gain === 0) {
							sortedParties.Equal.push(party);
						}
						else {
							sortedParties.Loss.push(party);
						}
					}
					sortGain(sortedParties.Gain);
					sortGain(sortedParties.Loss);					
					break;
				case "Merged":
					outerloop: for (var i=0,party;party=partiesInOrder[i];i+=1) {
						for (var j in sortedParties) {
							if (j === party.Party) {
								sortedParties[j].push(party);
								continue outerloop;
							}
						}
						var crit = partyData[party.Party].type[criterion];
						if (!sortedParties[crit]) {
							sortedParties[crit] = [];
							seats[crit] = 0;
						}
						sortedParties[crit].push(party);
						seats[crit] += party.Seats;
					}					
					break;
				case "Govt":
					for (var i=0,party;party=partiesInOrder[i];i+=1) {
						var value = 'Out';
						var govts = getGovernment();
						for (var j=0;j<govts.length;j+=1) {
							if (party.Party === govts[j]) {
								value = 'In';
								break;
							}
						}
						if (!sortedParties[value]) {
							sortedParties[value] = [];
						}
						sortedParties[value].push(party);
					}
					break;
				default:
					for (var i=0,party;party=partiesInOrder[i];i+=1) {
						var crit = partyData[party.Party].type[criterion];
						if (!sortedParties[crit]) {
							sortedParties[crit] = [];
							seats[crit] = 0;
							gain[crit] = 0;
						}
						sortedParties[crit].push(party);
						seats[crit] += party.Seats;
						gain[crit] += party.Gain;
					}
			}
			
			
			/*
				Check for party mergers
				Calculate total gain of new and merged parties,
				append merged parties next to new party, and
				change position of new party.

				Merged parties have zero seats by definition; new
				parties a positive gain by definition (before subtracting
				merged parties' losses)
			*/

			var pointers = {},
			toBeMoved = [],
			counters = {};

			for (var i in sortedParties) {
				setPointers(sortedParties[i]);
			}

			for (var i=0,party;party=partiesInOrder[i];i+=1) {
				if (!party.Seats && party.merged && pointers[party.merged]) {
					var move = {};
					move.from = party.Party;
					move.to = party.merged;
					toBeMoved.push(move);
					if (!counters[party.merged]) {
						counters[party.merged] = 0
					}
					counters[party.merged] += party.Gain;
				}
			}
			
			/* counters show gain (loss) of merged parties, but not of the party they merged into */
			
			for (var i in counters) {
				var partyObj = pointers[i].array[pointers[i].index];
				var oldGain = partyObj.Gain;
				partyObj.Gain += counters[i];
				if (criterion === 'Gain') {
					if (partyObj.Gain < 1) {
						var move = pointers[i].array.splice(pointers[i].index,1);
						setPointers(pointers[i].array);
						var targetArray = sortedParties.Loss;
						if (partyObj.Gain === 0) {
							targetArray = sortedParties.Equal;
						}
						for (var j=0;j<targetArray.length;j+=1) {
							if (targetArray[j].Gain >= partyObj.Gain
							&& targetArray[j].Seats >= partyObj.Seats)
								break;
						}
						targetArray.splice(j,0,move[0]);
						setPointers(targetArray);
					}
					pointers[i].array = sortGain(pointers[i].array);
				} 
				setPointers(pointers[i].array);
				partyObj.Gain = oldGain;
			}
			
			while (toBeMoved.length) {
				moveParty(toBeMoved.shift());
			}
			
			var sortedGroups = [];
			switch (criterion) {
				case "Seats":
					sortedGroups[0] = sortedParties.general;
					break;
				case "Gain":
					sortedGroups[2] = sortedParties.Gain;
					sortedGroups[1] = sortedParties.Equal;
					sortedGroups[0] = sortedParties.Loss;
					break;
				case "Govt":
					sortedGroups[1] = sortedParties.In;
					sortedGroups[0] = sortedParties.Out;
					break;
				default: 
					var calcArray = [];
					for (var i in sortedParties) {
						if (i === 'undefined') continue;
						calcArray.push(i);	
					}
					calcArray.sort(function (a,b) {
						var returnValue = seats[a] - seats[b];
						if (returnValue === 0) {
							returnValue = gain[a] - gain[b];
						}
						return returnValue;
					});
					if (sortedParties['undefined']) {
						sortedGroups[0] = sortedParties['undefined'];
					}
					for (var i=0;i<calcArray.length;i+=1) {
						sortedGroups[sortedGroups.length] = sortedParties[calcArray[i]];
					}
			}

			return sortedGroups;
			
			function setPointers(array) {
				for (var i=0;i<array.length;i+=1) {
					var party = array[i].Party;
					if (!pointers[party]) {
						pointers[party] = {};
					}
					pointers[party].array = array;
					pointers[party].index = i;
				}
			}
			
			function moveParty(move) {
				var from = pointers[move.from];
				var to = pointers[move.to];

				if (criterion !== 'Seats' && criterion !== 'Gain' && criterion !== 'Govt') {
					if (from.array !== to.array) {
						return;
					}
				}
				var toBeMoved = from.array.splice(from.index,1);
				setPointers(from.array);
				to.array.splice(to.index,0,toBeMoved[0]);
				setPointers(to.array);	
			}
			
			function sortGain(array) {
				return array.sort(function(a,b) {
					var result = a.Gain - b.Gain;
					if (result === 0) {
						result = a.Seats - b.Seats;
					}
					return result;
				});
			}
		}
		
		function addPlus(number) {
			var returnString = ''+number;
			if (number > 0) {
				returnString = '+' + returnString;
			}
			return returnString;
		}

		function createTable() {
			var table = document.createElement('table');
			table.className = 'politicsData';
			root = document.createElement('tbody');
			var header = document.createElement('tr');
			var sortCriteria = sortableOn.split(',');
			for (var i=0;i<sortCriteria.length;i+=1) {
				if (sortCriteria[i] === 'undefined') continue;
				createClickableHeader(sortCriteria[i]);
			}
			createClickableHeader('Comb. seats');
			createClickableHeader('Comb. gain');
			root.appendChild(header);
			table.appendChild(root);
			data.wrapper.appendChild(table);


			var oldSort;
			function createClickableHeader(text) {
				var th = document.createElement('th');
				th.innerHTML = text;
				if (text !== 'Party' && text.indexOf('Comb.') === -1) {
					th.style.cursor = 'pointer';
					th.onclick = function () {
						if (oldSort) {
							oldSort.className = '';
						}
						this.className = 'sort';
						currentCriterion = text;
						createView(currentCriterion);
						oldSort = this;
					}
				}
				if (text === currentCriterion) {
					th.className = 'sort';
					oldSort = th;
				}
				header.appendChild(th);
			}			
		}
		
	},
	
	/* 
		FACT SHEET 
		
		Creates a fact sheet.
		
		data may contain the following members:
			wrapper: 	HTML element the fact sheet is shown in
			party: 		party to be shown data of
			map: 		embed party map
			
		Wrapper is emptied (it may contain another fact sheet).
		Header is created.
		Then a lot of data elements are created from the party's data
		in partyData. Not much structuring here; just what works best.
		The data elements may contain links to other fact sheets,
		which are opened in the same HTML element.
	
	*/
	
	factSheet: function (data) {
		data.wrapper.innerHTML = '';
		data.wrapper.className = 'factsheet';

		var dataFinder = partyData[data.parties];

		var partyHeader = document.createElement('h5');
		partyHeader.className = 'factsheetHeader';
		partyHeader.innerHTML = data.parties + ' fact sheet';
		partyHeader.style.backgroundColor = dataFinder.color || defaultColor;
		data.wrapper.appendChild(partyHeader);

		var nameBlock = dataFinder.fullName + '<br><span class="smaller">' + dataFinder.englishName + '</span>';
		if (dataFinder.website) {
			nameBlock += '<br><a href="' + dataFinder.website + '" class="external">Website</a>';
		}
		createEntry(nameBlock);

		var stats = 'Denomination: ' + dataFinder.type.Denomination 
			+ '<br>Block: ' + dataFinder.type.Block;
		createEntry(stats);
		
		if (dataFinder.elections) {
			var active = createEntry('Active: ' + dataFinder.active);

			if (data.map && dataFinder.lastElections > 1917) {
				var embedData = {};
				embedData.wrapper = active;
				embedData.dataObject = data;
				embedData.callback = create.partyMap;
				embedData.text = 'map';
				embedData.dataObject.startyear = dataFinder.elections[0];
				embedData.dataObject.endyear = dataFinder.lastElections;
				this.embed(embedData);
			}
		}

		
		var mainText = dataFinder.text || 'No text written yet';
		var mainTextP = createEntry(mainText);
		mainTextP.className = 'mainText';

		if (dataFinder.links) {
			var links = createEntry('More information:<br>');
			for (var i=0,ref;ref=dataFinder.links[i];i+=1) {
				var lnk = document.createElement('a');
				lnk.href = ref.fullLink;
				lnk.innerHTML = ref.name;
				links.appendChild(lnk);
			}
		}
		
		
		if (dataFinder.relations) {
			var relations = ['splitto','mergedfrom','splitfrom','mergedto'];
			var texts = ['Split from','Predecessors','Split-offs','Merged into'];
			for (var i=0;i<relations.length;i+=1) {
				var list = dataFinder.relations[relations[i]];
				if (!list) continue;
				var text = createEntry(texts[i] + ': ');
				for (var j=0;j<list.length;j+=1) {
					var link = createLink(list[j]);
					text.appendChild(link);
					text.appendChild(document.createTextNode(' '));
				}
			}
		}
		
		function createLink(name) {
			var a = document.createElement('a');
			a.innerHTML = name;
			a.href = '#';
			a.onclick = function () {
				data.parties = name;
				create.factSheet(data);
				return false;
			}
			return a;
		}
		
		function createEntry(text) {
			var x = document.createElement('p');
			x.innerHTML = text;
			data.wrapper.appendChild(x);
			return x;
		}
	},
	
	/* FLOATING FACT SHEET */
	
	/*
		Creates a floating fact sheet (i.e. in the right column)
		Wrapper gets extra className to set CSS right.
		Create close button that removes the fact sheet from the document.
		Append close button, header and wrapper to floating fact sheet.
		Add wrapper and header as members to factSheet for other functions.
		Replace this function by a function that returns this floating
		fact sheet, so that any future call receives the same fact sheet.
	*/
	
	floatingFactSheet: function () {
		var factSheet = document.createElement('div');
		factSheet.className = 'wrapper facts';
		var innerWrapper = document.createElement('div');
		var header = document.createElement('h5');

		var close = document.createElement('a');
		close.className = 'openClose';
		close.href = '#';
		close.innerHTML = 'Close';
		close.onclick = function () {
			closeFactSheet();
			return false;
		}
		
		factSheet.appendChild(close);
		factSheet.appendChild(header);
		factSheet.appendChild(innerWrapper);
		factSheet.wrapper = innerWrapper;
		factSheet.header = header;
		
		function handleKeyUp(e) {
			var evt = e || window.event;
			var key = evt.keyCode;
			if (key === 27) {
				closeFactSheet();
			}
		}
		
		function closeFactSheet() {
			factSheet.parentNode.removeChild(factSheet);		
			document.onkeyup = null;
		}
		
		/* positionFromTop is global */

		function position () {
			var scrollOffset = document.documentElement.scrollTop || document.body.scrollTop;
			var pos = scrollOffset + positionFromTop;
			factSheet.style.top = pos + 'px';		
		}

		// just to show I can

		return (this.floatingFactSheet = function () {
			document.body.appendChild(factSheet);
			factSheet.wrapper.innerHTML = '';
			factSheet.header.innerHTML = '';
			position();
			document.onkeyup = handleKeyUp;
			return factSheet;
		})();
	},
	
	/* 
		FACT SHEET ondblclick on party name 
	
		Called as event handler when the user doubleclicks anywhere.

		Find user selection.
		If selection is a partyData member (i.e. a party)
			create a floating fact sheet
			create a new data object with
				wrapper: new floating fact sheet
				party: user selection
				map: on
			create fact sheet
		else if selection is a parliament member (i.e. an election year)
			create a floating fact sheet
			create a new data object with
				wrapper: new floating fact sheet
				year: user selection
			create fact sheet
	*/
	
	specialFactSheet: function () {
		var userSelection;
		if (window.getSelection) {
			var sel = window.getSelection();
			userSelection = sel.toString();
		}
		else if (document.selection) {
			var sel = document.selection.createRange();
			userSelection = sel.htmlText;
		}
		if (userSelection) {
			userSelection = userSelection.replace(/ /g,'');
			if (partyData[userSelection]) {
				var factsheet = create.floatingFactSheet();
				var dataObj = {};
				dataObj.wrapper = factsheet.wrapper;
				factsheet.header.style.display = 'none';
				dataObj.parties = userSelection;
				dataObj.map = true;
				create.factSheet(dataObj);
			}
			else if (data.datasrc[userSelection]) {
				var factsheet = create.floatingFactSheet();
				var dataObj = {};
				dataObj.wrapper = factsheet.wrapper;
				dataObj.year = userSelection;
				dataObj.clickthrough = 'blocked';
				dataObj.seatsInRow = 10;
				dataObj.totalWidth = 250;
				factsheet.header.innerHTML = headers.parliament(dataObj);
				create.parliament(dataObj);
			}
		}
	},

	/*
		SHOW FACT SHEET from graph
		
	*/
	
	showFactSheet: function (e) {
		var evt = e || window.event;
		var tgt	= evt.target || evt.srcElement;
		if (tgt.nodeName === 'SPAN') {
			tgt = tgt.parentNode;
		}
		if (tgt.party && partyData[tgt.party]) {
			var factsheet = create.floatingFactSheet();
			var dataObj = {};
			dataObj.wrapper = factsheet.wrapper;
			dataObj.parties = tgt.party;
			dataObj.header = factsheet.header;
			if (tgt.parentNode.showyear) {
				dataObj.showyear = tgt.parentNode.showyear;
			}
			dataObj.map = true;
			create.factSheet(dataObj);
		}
	},	

	/*
		Sets party name and seats in parliaments and seatGraphs.
		The text is created as a <span> and appended to the party <div>.
		If it doesn't fit, it's removed again and replaced by a tooltip.
		(A tooltip is triggered by the presence of the text property on the <div>)
		A party name is shown only once in a graph; after that only the number
		of seats are displayed.
		- rootElement is the root HTML element of the graph.
		- rootElement.texts is an object that keeps track of which party names
		  are already shown in the graph.
	*/

	setText: function(data) {
		var text = data.party + ' ' + data.seats;
		var originalText = text;
		var rootElement = data.element;
		while (rootElement.className.indexOf('wrapper') === -1
			&& rootElement.className.indexOf('parliament') === -1
		) {
			rootElement = rootElement.parentNode;
		}
		if (!rootElement.texts) {
			rootElement.texts = {};
		}
		if (data.overrule || rootElement.texts[data.party]) {
			text = data.seats;
		}
		var span = document.createElement('span');
		span.className = 'graphText';
		span.innerHTML = text;
		data.element.appendChild(span);

		if (span.offsetHeight > data.element.offsetHeight || span.offsetWidth > data.element.offsetWidth) {
			data.element.removeChild(span);
			data.element.toolTip = originalText;
		}
		else {
			rootElement.texts[data.party] = true;
		}
	},
	
	/* 
		EMBED
		
		To embed one kind of graph in another.

		data object contains:
			wrapper:	element to append the new graph to
			callback:	function to be called when new graph is
					created
			text:		text shown in open/close link
			dataObj:	the data object of the old graph
					that's mostly sent on to the embedded graph

		Receives the data object from the old graph.
		Creates a link that toggles open/close of the new graph
		and appends it to the wrapper of the old graph
		New graph closed by default.
		Creates a new wrapper and header for new graph.
		When new graph is opened, creates a new data object that largely
		copies the members of the old one, but assigns the newly created
		wrapper and header. Then it creates the new graph.
	*/

	embed: function (data) {
		var embeddedDiv = document.createElement('div'),
			openClose = document.createElement('a'),
			embedOpen = false;

		embeddedDiv.style.display = 'none';

		data.wrapper.appendChild(openClose);
		data.wrapper.appendChild(embeddedDiv);
		
		if (!data.dataObject.datasrc) {
			data.dataObject.datasrc = parliament;
		}

		openClose.innerHTML = 'View ' + data.text;
		openClose.className = 'open';
		openClose.href = '#';
		openClose.onclick = function () {
			(openClose.onclick = setInterface)();
			var dataObject = {};
			for (var i in data.dataObject) {
				dataObject[i] = data.dataObject[i];
			}
			dataObject.wrapper = embeddedDiv;
			dataObject.header = {};
			data.callback.call(create,dataObject);
			return false;
		}

		function setInterface() {
			if (!embedOpen) {
				embeddedDiv.style.display = 'block';
				openClose.innerHTML = 'Hide ' + data.text;
				openClose.className = 'open opened';
			}
			else {
				embeddedDiv.style.display = 'none';
				openClose.innerHTML = 'View ' + data.text;
				openClose.className = 'open';
			}
			embedOpen = !embedOpen;
			return false;
		}

	},

	/* UTILITIES */
	
	/* 	
		OPACITY
	
		For setting the opacity on maps.
		Is supposed handed a number from 1 to 10.
		If something's wrong with the number, returns
			lightest opacity
		Makes sure number is not larger than 10.
		Returns a class name for the image.
	*/
	
	setOpacity: function (number) {
		var className = 'op10';
		var opacity = Math.min(number,10);
		if (opacity) {
			var calculated = Math.round(opacity*2)*5;			
			className = 'op' + calculated;
		}
		return className;
	},
	
	/*
	
		PARTY LIST
	
		For creating a list of party names from a string
		such as "Denomination:Socialist+Economics:Right+!VVD+1994"
		Split on "+"
			If party name -> push to parties array
			If year -> take all parties in parliament in that year
			If criterion such as Denomination
				Loop through all parties' type list
				Check if party has that criterion
					Push to parties array
		Remove doubles from parties array
		Return parties array
	*/
	
	createPartyList: function (data) {
		if (data === 'all') {
			return partyOrder;
		}
		if (!data) {
			return;
		}
		var parties = [];
		var request = data.split('+');
		for (var j=0;j<request.length;j++) {
			var criterion = request[j].split(':');
			if (criterion.length < 2) {
				if (parliament[criterion[0]]) {
					var inParliament = parliament[criterion[0]].seats.national;
					for (var i in inParliament) {
						parties.push(i);
					}
				}
				else {
					parties.push(request[j]);
				}
			}
			else {
				for (var i=0;i<partyOrder.length;i++) {
					if (partyData[partyOrder[i]].type[criterion[0]] === criterion[1]) {
						parties.push(partyOrder[i]);
					}
				}
			}	
		}
		var checker = {};
		for (var i=0;i<parties.length;i+=1) {
			if (parties[i].charAt(0) === '!') {
				checker[parties[i].substring(1)] = true;			
			}
		}
		for (var i=0;i<parties.length;i+=1) {
			if (checker[parties[i]]) {
				parties.splice(i,1);
				i -= 1;
			} else {
				checker[parties[i]] = true;
			}
		}
		return parties;
	},
	
	/*
	
		YEARS
	
		Calculate the start and end years.
		This is mainly for the polls, which appear every week and which have
		a 'year' like 15/2
		
		Returns an array that's used in the main function
		
	*/
	
	getAllYears: function (data) {
		var years = [];
		var lastYear = data.datasrc.order[data.datasrc.order.length-1];
		var isYear = !isNaN(lastYear*1);
		if (isYear) {
			var start = data.startyear || standardstartyear;
			var end = data.endyear || lastYear;
			for (var i=0;i<data.datasrc.order.length;i+=1) {
				var year = data.datasrc.order[i];
				if (year >= start && year <= end) {
					years.push(year);
				}
			}
		} else {
			for (var i=0;i<data.datasrc.order.length;i+=1) {
				years.push(data.datasrc.order[i]);
			}
		}
		return years;
	}
};


function provinceToSystem(name) {
	var provinceName = name.toLowerCase();
	provinceName = provinceName.replace(/-/g,'');
	return provinceName;
}

/* Keep this global (don't ask) */

function showToolTip(e) {
	if (!showToolTip.tip) {
		showToolTip.tip = document.createElement('div');
		showToolTip.tip.id = 'tooltip';
		showToolTip.tip.onmouseout = showToolTip;
		document.body.appendChild(showToolTip.tip);
	}
	var tip = showToolTip.tip;
	tip.style.visibility = 'hidden';
	var evt = e || window.event;
	var tgt = evt.target || evt.srcElement;
	if (!tgt.toolTip) return;
	tip.innerHTML = tgt.toolTip;
	var pos = findPos(tgt);
	if (tgt.parentNode.className === 'yearWrapper') {
		tip.style.top = pos[1] + 1 + 'px';
		tip.style.left = pos[0] + Math.round(tgt.offsetWidth/4)*3  + 'px';
	} else {
		tip.style.left = pos[0] + 'px';
		tip.style.top = pos[1] + tgt.offsetHeight - tip.offsetHeight/2 + 'px';	
	}
	tip.style.backgroundColor = tgt.style.backgroundColor;
	tip.style.visibility = 'visible';
}

function findPos(obj) {
	var objLeft = 0,objTop = 0;
	if (!obj.offsetParent) return []; // position not measurable
	do {
		objLeft += obj.offsetLeft;
		objTop += obj.offsetTop;
	} while (obj = obj.offsetParent);
	return [objLeft,objTop];
}

/* COOKIES */

var Cookies = {
	init: function () {
		var allCookies = document.cookie.split('; ');
		for (var i=0;i<allCookies.length;i++) {
			var cookiePair = allCookies[i].split('=');
			this[cookiePair[0]] = cookiePair[1];
		}
	},
	create: function (name,value,days) {
		if (days) {
			var date = new Date();
			date.setTime(date.getTime()+(days*24*60*60*1000));
			var expires = "; expires="+date.toGMTString();
		}
		else var expires = "";
		document.cookie = name+"="+value+expires+"; path=/";
		this[name] = value;
	},
	erase: function (name) {
		this.create(name,'',-1);
		this[name] = undefined;
	}
};
Cookies.init();

/* CONTAINS FOR FIREFOX */

if (window.Node && Node.prototype && !Node.prototype.contains) {
	Node.prototype.contains = function (arg) {
		return !!(this === arg || this.compareDocumentPosition(arg) & 16)
	}
}

/* INITIALIZATION (doesn't wait for onload) */

(function () {
	if (window.setChapterTitles) {
		setChapterTitles(chapters,'chapters');
		setChapterTitles(documentation,'documentation');
	}
	initialiseApp();
})();

