Mercurial > hg > mpiwg_geobrowser
view lib/GeoTemCo/js/FuzzyTimeline/FuzzyTimelineDensity.js @ 3:19f75fe342eb
minor changes
author | Dirk Wintergruen <dwinter@mpiwg-berlin.mpg.de> |
---|---|
date | Mon, 12 Oct 2015 08:33:28 +0200 |
parents | b57c7821382f |
children |
line wrap: on
line source
/* * 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() { } };