diff geotemco/js/FuzzyTimeline/FuzzyTimelineDensity.js @ 0:b12c99b7c3f0

commit for previous development
author Zoe Hong <zhong@mpiwg-berlin.mpg.de>
date Mon, 19 Jan 2015 17:13:49 +0100
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/geotemco/js/FuzzyTimeline/FuzzyTimelineDensity.js	Mon Jan 19 17:13:49 2015 +0100
@@ -0,0 +1,540 @@
+/*
+* FuzzyTimelineDensity.js
+*
+* Copyright (c) 2013, Sebastian Kruse. All rights reserved.
+*
+* This library is free software; you can redistribute it and/or
+* modify it under the terms of the GNU Lesser General Public
+* License as published by the Free Software Foundation; either
+* version 3 of the License, or (at your option) any later version.
+*
+* This library is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+* Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public
+* License along with this library; if not, write to the Free Software
+* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+* MA 02110-1301  USA
+*/
+
+/**
+ * @class FuzzyTimelineDensity
+ * Implementation for a fuzzy time-ranges density plot
+ * @author Sebastian Kruse (skruse@mpiwg-berlin.mpg.de)
+ *
+ * @param {HTML object} parent div to append the FuzzyTimeline
+ */
+function FuzzyTimelineDensity(parent,div) {
+
+	this.index;
+	this.fuzzyTimeline = this;
+	this.singleTickWidth;
+	this.singleTickCenter = function(){return this.singleTickWidth/2;};
+	//contains all data
+	this.datasetsPlot;
+	this.datasetsHash;
+	this.highlightedDatasetsPlot;
+	this.yValMin;
+	this.yValMax;
+	this.displayType;
+	//contains selected data
+	this.selected = undefined;
+	//contains the last selected "date"
+	this.highlighted;
+	
+	this.parent = parent;
+	this.div = div;
+	this.options = parent.options;
+	this.plot;
+	this.maxTickCount = this.options.maxDensityTicks;
+	
+	this.datasets;
+}
+
+FuzzyTimelineDensity.prototype = {
+
+	initialize : function(datasets) {
+		var density = this;
+			
+		density.datasets = datasets;
+		density.selected = [];
+	},
+	
+	createPlot : function(data){
+		density = this;
+		var chartData = [];
+
+		chartData.push([density.parent.overallMin,0]);
+		$.each(data, function(name,val){
+			var tickCenterTime = density.parent.overallMin+name*density.singleTickWidth+density.singleTickCenter();
+			var dateObj = moment(tickCenterTime);
+			chartData.push([dateObj,val]);
+		});
+		var maxPlotedDate = chartData[chartData.length-1][0];
+		if (density.parent.overallMax > maxPlotedDate){
+			chartData.push([density.parent.overallMax,0]);
+		} else {
+			chartData.push([maxPlotedDate+1,0]);
+		}
+		
+
+		
+		return chartData;
+	},
+	
+	//uniform distribution (UD)	
+	createUDData : function(datasets) {
+		var density = this;
+		var plots = [];
+		var objectHashes = [];
+		$(datasets).each(function(){
+			var chartDataCounter = new Object();
+			var objectHash = new Object();
+
+			for (var i = 0; i < density.tickCount; i++){
+				chartDataCounter[i]=0;
+			}
+			//check if we got "real" datasets, or just array of objects
+			var datasetObjects = this;
+			if (typeof this.objects !== "undefined")
+				datasetObjects = this.objects;
+			$(datasetObjects).each(function(){
+				var ticks = density.parent.getTicks(this, density.singleTickWidth);
+				if (typeof ticks !== "undefined"){
+					var exactTickCount = 
+						ticks.firstTickPercentage+
+						ticks.lastTickPercentage+
+						(ticks.lastTick-ticks.firstTick-1);
+					for (var i = ticks.firstTick; i <= ticks.lastTick; i++){
+						var weight = 0;
+						//calculate the weight for each span, that the object overlaps
+						if (density.parent.options.timelineMode == 'fuzzy'){
+							//in fuzzy mode, each span gets just a fraction of the complete weight
+							if (i == ticks.firstTick)
+								weight = this.weight * ticks.firstTickPercentage/exactTickCount;
+							else if (i == ticks.lastTick)
+								weight = this.weight * ticks.lastTickPercentage/exactTickCount;
+							else
+								weight = this.weight * 1/exactTickCount;
+						} else if (density.parent.options.timelineMode == 'stacking'){
+							//in stacking mode each span gets the same amount.
+							//(besides first and last..)
+							if (i == ticks.firstTick)
+								weight = this.weight * ticks.firstTickPercentage;
+							else if (i == ticks.lastTick)
+								weight = this.weight * ticks.lastTickPercentage;
+							else
+								weight = this.weight;
+						}
+						
+						chartDataCounter[i] += weight;
+						//add this object to the hash
+						if (typeof objectHash[i] === "undefined")
+							objectHash[i] = [];
+						objectHash[i].push(this);
+					}
+				}
+			});
+			
+			//scale according to selected type
+			chartDataCounter = density.parent.scaleData(chartDataCounter);
+			
+			var udChartData = density.createPlot(chartDataCounter);
+			if (udChartData.length > 0)
+				plots.push(udChartData);
+			
+			objectHashes.push(objectHash);			
+		});
+		
+		return {plots:plots, hashs:objectHashes};
+	},
+	
+	showPlot : function() {
+		var density = this;
+		var plot = density.datasetsPlot;
+		var highlight_select_plot = $.merge([],plot);
+		
+		//see if there are selected/highlighted values
+		if (density.highlightedDatasetsPlot instanceof Array){
+			//check if plot is some other - external - graph
+			if (plot === density.datasetsPlot)
+				highlight_select_plot = $.merge(highlight_select_plot,density.highlightedDatasetsPlot);
+		}
+		
+		var axisFormatString = "%Y";
+		var tooltipFormatString = "YYYY";
+		if (density.singleTickWidth<60*1000){
+			axisFormatString = "%Y/%m/%d %H:%M:%S";
+			tooltipFormatString = "YYYY/MM/DD HH:mm:ss";
+		} else if (density.singleTickWidth<60*60*1000) {
+			axisFormatString = "%Y/%m/%d %H:%M";
+			tooltipFormatString = "YYYY/MM/DD HH:mm";
+		} else if (density.singleTickWidth<24*60*60*1000){
+			axisFormatString = "%Y/%m/%d %H";
+			tooltipFormatString = "YYYY/MM/DD HH";
+		} else if (density.singleTickWidth<31*24*60*60*1000){
+			axisFormatString = "%Y/%m/%d";
+			tooltipFormatString = "YYYY/MM/DD";
+		} else if (density.singleTickWidth<12*31*24*60*60*1000){
+			axisFormatString = "%Y/%m";
+			tooltipFormatString = "YYYY/MM";
+		}
+
+		//credits: Pimp Trizkit @ http://stackoverflow.com/a/13542669
+		function shadeRGBColor(color, percent) {
+		    var f=color.split(","),t=percent<0?0:255,p=percent<0?percent*-1:percent,R=parseInt(f[0].slice(4)),G=parseInt(f[1]),B=parseInt(f[2]);
+		    return "rgb("+(Math.round((t-R)*p)+R)+","+(Math.round((t-G)*p)+G)+","+(Math.round((t-B)*p)+B)+")";
+		}
+		
+		//credits: Tupak Goliam @ http://stackoverflow.com/a/3821786
+        var drawLines = function(plot, ctx) {
+            var data = plot.getData();
+            var axes = plot.getAxes();
+            var offset = plot.getPlotOffset();
+            for (var i = 0; i < data.length; i++) {
+                var series = data[i];
+                var lineWidth = 1;
+                
+                for (var j = 0; j < series.data.length-1; j++) {
+                    var d = (series.data[j]);
+                    var d2 = (series.data[j+1]);
+                    
+                    var x = offset.left + axes.xaxis.p2c(d[0]);
+                    var y = offset.top + axes.yaxis.p2c(d[1]);
+                    
+                    var x2 = offset.left + axes.xaxis.p2c(d2[0]);
+                    var y2 = offset.top + axes.yaxis.p2c(d2[1]);
+
+                    //hide lines that "connect" 0 and 0
+                    //essentially blanking out the 0 values 
+                    if ((d[1]==0)&&(d2[1]==0)){
+                        continue;
+                    }
+                    
+                    ctx.strokeStyle=series.color;
+                    ctx.lineWidth = lineWidth;
+                    ctx.beginPath();
+                    ctx.moveTo(x,y);
+                    ctx.lineTo(x2,y2);
+
+                    //add shadow (esp. to make background lines more visible)
+                    ctx.shadowColor = shadeRGBColor(series.color,-0.3);
+                    ctx.shadowBlur=1;
+                    ctx.shadowOffsetX = 1; 
+                    ctx.shadowOffsetY = 1;
+                    
+                    ctx.stroke();
+                }    
+            }
+        }; 	
+
+		var options = {
+				series:{
+					//width:0 because line is drawn in own routine above
+					//but everything else (data points, shadow) should be drawn
+	                lines:{show: true, lineWidth: 0, shadowSize: 0},
+	            },
+				grid: {
+		            hoverable: true,
+		            clickable: true,
+			        backgroundColor: density.parent.options.backgroundColor,
+			        borderWidth: 0,
+			        minBorderMargin: 0,
+		        },
+		        legend: {
+		        },
+		        tooltip: true,
+		        tooltipOpts: {
+		            content: function(label, xval, yval, flotItem){
+		            	highlightString =	moment(xval-density.singleTickCenter()).format(tooltipFormatString) + " - " +
+		            						moment(xval+density.singleTickCenter()).format(tooltipFormatString) + " : ";
+		            	//(max.)2 Nachkomma-Stellen von y-Wert anzeigen
+		            	highlightString +=	Math.round(yval*100)/100; 
+
+		        		return highlightString;
+		            }
+		        },
+		        selection: { 
+		        	mode: "x"
+		        },
+				xaxis: {
+					mode: "time",
+					timeformat:axisFormatString,
+					min : density.parent.overallMin, 
+					max : density.parent.overallMax,
+				},
+		        yaxis: {
+		        	min : density.yValMin,
+		        	max : density.yValMax*1.05
+		        },
+                hooks: { 
+                    draw  : drawLines
+                },
+			};
+		if (!density.parent.options.showYAxis)
+			options.yaxis.show=false;
+		
+		var highlight_select_plot_colors = [];		
+		var i = 0;
+		$(highlight_select_plot).each(function(){
+			var color;
+			if (i < GeoTemConfig.datasets.length){
+				var datasetColors = GeoTemConfig.getColor(i);
+				if (highlight_select_plot.length>GeoTemConfig.datasets.length)
+					color = "rgb("+datasetColors.r0+","+datasetColors.g0+","+datasetColors.b0+")";
+				else 
+					color = "rgb("+datasetColors.r1+","+datasetColors.g1+","+datasetColors.b1+")";
+			} else {
+				var datasetColors = GeoTemConfig.getColor(i-GeoTemConfig.datasets.length);
+				color = "rgb("+datasetColors.r1+","+datasetColors.g1+","+datasetColors.b1+")";
+			}			
+			
+			highlight_select_plot_colors.push({
+				color : color,
+				data : this
+			});
+			i++;
+		});
+		
+		density.plot = $.plot($(density.div), highlight_select_plot_colors, options);
+		density.parent.drawHandles();
+		
+		var rangeBars = density.parent.rangeBars;
+		if (typeof rangeBars !== "undefined")
+			$(density.div).unbind("plothover", rangeBars.hoverFunction);
+		$(density.div).unbind("plothover", density.hoverFunction);
+	    $(density.div).bind("plothover", density.hoverFunction);
+
+	    //this var prevents the execution of the plotclick event after a select event 
+	    density.wasSelection = false;
+	    $(density.div).unbind("plotclick");
+	    $(density.div).bind("plotclick", density.clickFunction);
+	    
+	    $(density.div).unbind("plotselected");
+	    $(density.div).bind("plotselected", density.selectFuntion);
+	},
+	
+	hoverFunction : function (event, pos, item) {
+    	var hoverPoint;
+    	//TODO: this could be wanted (if negative weight is used)
+        if ((item)&&(item.datapoint[1] != 0)) {
+        	//at begin and end of plot there are added 0 points
+        	hoverPoint = item.dataIndex-1;
+        }
+        //remember last point, so that we don't redraw the current state
+        //that "hoverPoint" may be undefined is on purpose
+    	if (density.highlighted !== hoverPoint){
+			density.highlighted = hoverPoint;
+        	density.triggerHighlight(hoverPoint);
+        }
+    },
+    
+    clickFunction : function (event, pos, item) {
+    	if (density.wasSelection)
+    		density.wasSelection = false;
+    	else {
+        	//remove selection handles (if there were any)
+        	density.parent.clearHandles();
+	    	
+	    	var selectPoint;
+	        //that date may be undefined is on purpose	    	
+	    	//TODO: ==0 could be wanted (if negative weight is used)
+	        if ((item)&&(item.datapoint[1] != 0)) {
+	        	//at begin and end of plot there are added 0 points
+	        	selectPoint = item.dataIndex-1;
+	        }
+        	density.triggerSelection(selectPoint);
+        }
+    },
+    
+    selectFuntion : function(event, ranges) {
+    	var spanArray = density.parent.getSpanArray(density.singleTickWidth);
+    	var startSpan, endSpan;
+    	for (var i = 0; i < spanArray.length-1; i++){
+    		if ((typeof startSpan === "undefined") && (ranges.xaxis.from <= spanArray[i+1]))
+    			startSpan = i;
+    		if ((typeof endSpan === "undefined") && (ranges.xaxis.to <= spanArray[i+1]))
+    			endSpan = i;
+    	}
+    	
+    	if ((typeof startSpan !== "undefined") && (typeof endSpan !== "undefined")){
+        	density.triggerSelection(startSpan, endSpan);
+	    	density.wasSelection = true;
+
+	    	density.parent.clearHandles();
+	    	var xaxis = density.plot.getAxes().xaxis;
+	    	var x1 = density.plot.pointOffset({x:ranges.xaxis.from,y:0}).left;
+	    	var x2 = density.plot.pointOffset({x:ranges.xaxis.to,y:0}).left;
+	    	
+	    	density.parent.addHandle(x1,x2);
+    	}
+    },
+	
+	selectByX : function(x1, x2){
+		density = this;
+		var xaxis = density.plot.getAxes().xaxis;
+		var offset = density.plot.getPlotOffset().left;
+    	var from = xaxis.c2p(x1-offset);
+    	var to = xaxis.c2p(x2-offset);
+
+    	var spanArray = density.parent.getSpanArray(density.singleTickWidth);
+    	var startSpan, endSpan;
+    	for (var i = 0; i < spanArray.length-1; i++){
+    		if ((typeof startSpan === "undefined") && (from <= spanArray[i+1]))
+    			startSpan = i;
+    		if ((typeof endSpan === "undefined") && (to <= spanArray[i+1]))
+    			endSpan = i;
+    	}
+    	
+    	if ((typeof startSpan !== "undefined") && (typeof endSpan !== "undefined")){
+    		density.triggerSelection(startSpan, endSpan);
+    	}
+	},
+	
+	drawDensityPlot : function(datasets, tickWidth) {
+		var density = this;
+		//calculate tick width (will be in ms)
+		delete density.tickCount;
+		delete density.singleTickWidth;
+		delete density.highlightedDatasetsPlot;
+		density.parent.zoomPlot(1);
+		if (typeof tickWidth !== "undefined"){
+			density.singleTickWidth = tickWidth;
+			density.tickCount = Math.ceil((density.parent.overallMax-density.parent.overallMin)/tickWidth);
+		} 
+		if ((typeof density.tickCount === "undefined") || (density.tickCount > density.maxTickCount)){
+			density.tickCount = density.maxTickCount;
+			density.singleTickWidth = (density.parent.overallMax-density.parent.overallMin)/density.tickCount;
+			if (density.singleTickWidth === 0)
+				density.singleTickWidth = 1;
+		}
+		
+		var hashAndPlot = density.createUDData(datasets);
+		
+		density.datasetsPlot = hashAndPlot.plots;
+		density.datasetsHash = hashAndPlot.hashs;
+
+		density.yValMin = 0;
+		density.yValMax = 0;
+		
+		density.combinedDatasetsPlot = [];
+		for (var i = 0; i < density.datasetsPlot.length; i++){
+			for (var j = 0; j < density.datasetsPlot[i].length; j++){
+				var val = density.datasetsPlot[i][j][1];
+				
+				if (val < density.yValMin)
+					density.yValMin = val;
+				if (val > density.yValMax)
+					density.yValMax = val;				
+			}
+		}
+		
+	    density.showPlot();
+	},
+	
+	triggerHighlight : function(hoverPoint) {
+		var density = this;
+		var highlightedObjects = [];
+		
+
+		if (typeof hoverPoint !== "undefined") {
+			$(density.datasetsHash).each(function(){
+				if (typeof this[hoverPoint] !== "undefined")
+					highlightedObjects.push(this[hoverPoint]);
+				else
+					highlightedObjects.push([]);
+			});
+		} else {
+			for (var i = 0; i < GeoTemConfig.datasets.length; i++)
+				highlightedObjects.push([]);
+		}
+		this.parent.core.triggerHighlight(highlightedObjects);
+	},
+
+	triggerSelection : function(startPoint, endPoint) {
+		var density = this;
+		var selection;
+		if (typeof startPoint !== "undefined") {
+			if (typeof endPoint === "undefined")
+				endPoint = startPoint;
+			density.selected = [];
+			$(density.datasetsHash).each(function(){
+				var objects = [];
+				for (var i = startPoint; i <= endPoint; i++){
+					$(this[i]).each(function(){
+						if ($.inArray(this, objects) == -1){
+							objects.push(this);
+						}
+					});
+				}				
+				density.selected.push(objects);
+			});
+
+			selection = new Selection(density.selected, density.parent);
+		} else {
+			//empty selection
+			density.selected = [];
+			for (var i = 0; i < GeoTemConfig.datasets.length; i++)
+				density.selected.push([]);
+			selection = new Selection(density.selected);
+		}
+		
+		this.parent.selectionChanged(selection);
+		this.parent.core.triggerSelection(selection);
+	},
+	
+	highlightChanged : function(objects) {
+		if( !GeoTemConfig.highlightEvents ){
+			return;
+		}
+		var density = this;
+		var emptyHighlight = true;
+		var selected_highlighted = objects;
+		if (typeof density.selected !== "undefined")
+			selected_highlighted = GeoTemConfig.mergeObjects(objects,density.selected);
+		$(selected_highlighted).each(function(){
+			if ((this instanceof Array) && (this.length > 0)){
+				emptyHighlight = false;
+				return false;
+			}
+		});
+		if (emptyHighlight && (typeof density.selected === "undefined")){
+			density.highlightedDatasetsPlot = [];
+		} else {
+			density.highlightedDatasetsPlot = density.createUDData(selected_highlighted).plots;
+		}
+		density.showPlot();
+	},
+	
+	selectionChanged : function(objects) {
+		if( !GeoTemConfig.selectionEvents ){
+			return;
+		}
+		var density = this;
+		density.selected = objects;
+		density.highlightChanged([]);
+	},
+
+	deselection : function() {
+	},
+
+	filtering : function() {
+	},
+
+	inverseFiltering : function() {
+	},
+
+	triggerRefining : function() {
+	},
+
+	reset : function() {
+	},
+	
+	show : function() {		
+	},
+
+	hide : function() {
+	}
+};