Mercurial > hg > LGMap
comparison geotemco/js/FuzzyTimeline/FuzzyTimelineDensity.js @ 0:57bde4830927
first commit
author | Zoe Hong <zhong@mpiwg-berlin.mpg.de> |
---|---|
date | Tue, 24 Mar 2015 11:37:17 +0100 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:57bde4830927 |
---|---|
1 /* | |
2 * FuzzyTimelineDensity.js | |
3 * | |
4 * Copyright (c) 2013, Sebastian Kruse. All rights reserved. | |
5 * | |
6 * This library is free software; you can redistribute it and/or | |
7 * modify it under the terms of the GNU Lesser General Public | |
8 * License as published by the Free Software Foundation; either | |
9 * version 3 of the License, or (at your option) any later version. | |
10 * | |
11 * This library is distributed in the hope that it will be useful, | |
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
14 * Lesser General Public License for more details. | |
15 * | |
16 * You should have received a copy of the GNU Lesser General Public | |
17 * License along with this library; if not, write to the Free Software | |
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, | |
19 * MA 02110-1301 USA | |
20 */ | |
21 | |
22 /** | |
23 * @class FuzzyTimelineDensity | |
24 * Implementation for a fuzzy time-ranges density plot | |
25 * @author Sebastian Kruse (skruse@mpiwg-berlin.mpg.de) | |
26 * | |
27 * @param {HTML object} parent div to append the FuzzyTimeline | |
28 */ | |
29 function FuzzyTimelineDensity(parent,div) { | |
30 | |
31 this.index; | |
32 this.fuzzyTimeline = this; | |
33 this.singleTickWidth; | |
34 this.singleTickCenter = function(){return this.singleTickWidth/2;}; | |
35 //contains all data | |
36 this.datasetsPlot; | |
37 this.datasetsHash; | |
38 this.highlightedDatasetsPlot; | |
39 this.yValMin; | |
40 this.yValMax; | |
41 this.displayType; | |
42 //contains selected data | |
43 this.selected = undefined; | |
44 //contains the last selected "date" | |
45 this.highlighted; | |
46 | |
47 this.parent = parent; | |
48 this.div = div; | |
49 this.options = parent.options; | |
50 this.plot; | |
51 this.maxTickCount = this.options.maxDensityTicks; | |
52 | |
53 this.datasets; | |
54 } | |
55 | |
56 FuzzyTimelineDensity.prototype = { | |
57 | |
58 initialize : function(datasets) { | |
59 var density = this; | |
60 | |
61 density.datasets = datasets; | |
62 density.selected = []; | |
63 }, | |
64 | |
65 createPlot : function(data){ | |
66 density = this; | |
67 var chartData = []; | |
68 | |
69 chartData.push([density.parent.overallMin,0]); | |
70 $.each(data, function(name,val){ | |
71 var tickCenterTime = density.parent.overallMin+name*density.singleTickWidth+density.singleTickCenter(); | |
72 var dateObj = moment(tickCenterTime); | |
73 chartData.push([dateObj,val]); | |
74 }); | |
75 var maxPlotedDate = chartData[chartData.length-1][0]; | |
76 if (density.parent.overallMax > maxPlotedDate){ | |
77 chartData.push([density.parent.overallMax,0]); | |
78 } else { | |
79 chartData.push([maxPlotedDate+1,0]); | |
80 } | |
81 | |
82 | |
83 | |
84 return chartData; | |
85 }, | |
86 | |
87 //uniform distribution (UD) | |
88 createUDData : function(datasets) { | |
89 var density = this; | |
90 var plots = []; | |
91 var objectHashes = []; | |
92 $(datasets).each(function(){ | |
93 var chartDataCounter = new Object(); | |
94 var objectHash = new Object(); | |
95 | |
96 for (var i = 0; i < density.tickCount; i++){ | |
97 chartDataCounter[i]=0; | |
98 } | |
99 //check if we got "real" datasets, or just array of objects | |
100 var datasetObjects = this; | |
101 if (typeof this.objects !== "undefined") | |
102 datasetObjects = this.objects; | |
103 $(datasetObjects).each(function(){ | |
104 var ticks = density.parent.getTicks(this, density.singleTickWidth); | |
105 if (typeof ticks !== "undefined"){ | |
106 var exactTickCount = | |
107 ticks.firstTickPercentage+ | |
108 ticks.lastTickPercentage+ | |
109 (ticks.lastTick-ticks.firstTick-1); | |
110 for (var i = ticks.firstTick; i <= ticks.lastTick; i++){ | |
111 var weight = 0; | |
112 //calculate the weight for each span, that the object overlaps | |
113 if (density.parent.options.timelineMode == 'fuzzy'){ | |
114 //in fuzzy mode, each span gets just a fraction of the complete weight | |
115 if (i == ticks.firstTick) | |
116 weight = this.weight * ticks.firstTickPercentage/exactTickCount; | |
117 else if (i == ticks.lastTick) | |
118 weight = this.weight * ticks.lastTickPercentage/exactTickCount; | |
119 else | |
120 weight = this.weight * 1/exactTickCount; | |
121 } else if (density.parent.options.timelineMode == 'stacking'){ | |
122 //in stacking mode each span gets the same amount. | |
123 //(besides first and last..) | |
124 if (i == ticks.firstTick) | |
125 weight = this.weight * ticks.firstTickPercentage; | |
126 else if (i == ticks.lastTick) | |
127 weight = this.weight * ticks.lastTickPercentage; | |
128 else | |
129 weight = this.weight; | |
130 } | |
131 | |
132 chartDataCounter[i] += weight; | |
133 //add this object to the hash | |
134 if (typeof objectHash[i] === "undefined") | |
135 objectHash[i] = []; | |
136 objectHash[i].push(this); | |
137 } | |
138 } | |
139 }); | |
140 | |
141 //scale according to selected type | |
142 chartDataCounter = density.parent.scaleData(chartDataCounter); | |
143 | |
144 var udChartData = density.createPlot(chartDataCounter); | |
145 if (udChartData.length > 0) | |
146 plots.push(udChartData); | |
147 | |
148 objectHashes.push(objectHash); | |
149 }); | |
150 | |
151 return {plots:plots, hashs:objectHashes}; | |
152 }, | |
153 | |
154 showPlot : function() { | |
155 var density = this; | |
156 var plot = density.datasetsPlot; | |
157 var highlight_select_plot = $.merge([],plot); | |
158 | |
159 //see if there are selected/highlighted values | |
160 if (density.highlightedDatasetsPlot instanceof Array){ | |
161 //check if plot is some other - external - graph | |
162 if (plot === density.datasetsPlot) | |
163 highlight_select_plot = $.merge(highlight_select_plot,density.highlightedDatasetsPlot); | |
164 } | |
165 | |
166 var axisFormatString = "%Y"; | |
167 var tooltipFormatString = "YYYY"; | |
168 if (density.singleTickWidth<60*1000){ | |
169 axisFormatString = "%Y/%m/%d %H:%M:%S"; | |
170 tooltipFormatString = "YYYY/MM/DD HH:mm:ss"; | |
171 } else if (density.singleTickWidth<60*60*1000) { | |
172 axisFormatString = "%Y/%m/%d %H:%M"; | |
173 tooltipFormatString = "YYYY/MM/DD HH:mm"; | |
174 } else if (density.singleTickWidth<24*60*60*1000){ | |
175 axisFormatString = "%Y/%m/%d %H"; | |
176 tooltipFormatString = "YYYY/MM/DD HH"; | |
177 } else if (density.singleTickWidth<31*24*60*60*1000){ | |
178 axisFormatString = "%Y/%m/%d"; | |
179 tooltipFormatString = "YYYY/MM/DD"; | |
180 } else if (density.singleTickWidth<12*31*24*60*60*1000){ | |
181 axisFormatString = "%Y/%m"; | |
182 tooltipFormatString = "YYYY/MM"; | |
183 } | |
184 | |
185 //credits: Pimp Trizkit @ http://stackoverflow.com/a/13542669 | |
186 function shadeRGBColor(color, percent) { | |
187 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]); | |
188 return "rgb("+(Math.round((t-R)*p)+R)+","+(Math.round((t-G)*p)+G)+","+(Math.round((t-B)*p)+B)+")"; | |
189 } | |
190 | |
191 //credits: Tupak Goliam @ http://stackoverflow.com/a/3821786 | |
192 var drawLines = function(plot, ctx) { | |
193 var data = plot.getData(); | |
194 var axes = plot.getAxes(); | |
195 var offset = plot.getPlotOffset(); | |
196 for (var i = 0; i < data.length; i++) { | |
197 var series = data[i]; | |
198 var lineWidth = 1; | |
199 | |
200 for (var j = 0; j < series.data.length-1; j++) { | |
201 var d = (series.data[j]); | |
202 var d2 = (series.data[j+1]); | |
203 | |
204 var x = offset.left + axes.xaxis.p2c(d[0]); | |
205 var y = offset.top + axes.yaxis.p2c(d[1]); | |
206 | |
207 var x2 = offset.left + axes.xaxis.p2c(d2[0]); | |
208 var y2 = offset.top + axes.yaxis.p2c(d2[1]); | |
209 | |
210 //hide lines that "connect" 0 and 0 | |
211 //essentially blanking out the 0 values | |
212 if ((d[1]==0)&&(d2[1]==0)){ | |
213 continue; | |
214 } | |
215 | |
216 ctx.strokeStyle=series.color; | |
217 ctx.lineWidth = lineWidth; | |
218 ctx.beginPath(); | |
219 ctx.moveTo(x,y); | |
220 ctx.lineTo(x2,y2); | |
221 | |
222 //add shadow (esp. to make background lines more visible) | |
223 ctx.shadowColor = shadeRGBColor(series.color,-0.3); | |
224 ctx.shadowBlur=1; | |
225 ctx.shadowOffsetX = 1; | |
226 ctx.shadowOffsetY = 1; | |
227 | |
228 ctx.stroke(); | |
229 } | |
230 } | |
231 }; | |
232 | |
233 var options = { | |
234 series:{ | |
235 //width:0 because line is drawn in own routine above | |
236 //but everything else (data points, shadow) should be drawn | |
237 lines:{show: true, lineWidth: 0, shadowSize: 0}, | |
238 }, | |
239 grid: { | |
240 hoverable: true, | |
241 clickable: true, | |
242 backgroundColor: density.parent.options.backgroundColor, | |
243 borderWidth: 0, | |
244 minBorderMargin: 0, | |
245 }, | |
246 legend: { | |
247 }, | |
248 tooltip: true, | |
249 tooltipOpts: { | |
250 content: function(label, xval, yval, flotItem){ | |
251 highlightString = moment(xval-density.singleTickCenter()).format(tooltipFormatString) + " - " + | |
252 moment(xval+density.singleTickCenter()).format(tooltipFormatString) + " : "; | |
253 //(max.)2 Nachkomma-Stellen von y-Wert anzeigen | |
254 highlightString += Math.round(yval*100)/100; | |
255 | |
256 return highlightString; | |
257 } | |
258 }, | |
259 selection: { | |
260 mode: "x" | |
261 }, | |
262 xaxis: { | |
263 mode: "time", | |
264 timeformat:axisFormatString, | |
265 min : density.parent.overallMin, | |
266 max : density.parent.overallMax, | |
267 }, | |
268 yaxis: { | |
269 min : density.yValMin, | |
270 max : density.yValMax*1.05 | |
271 }, | |
272 hooks: { | |
273 draw : drawLines | |
274 }, | |
275 }; | |
276 if (!density.parent.options.showYAxis) | |
277 options.yaxis.show=false; | |
278 | |
279 var highlight_select_plot_colors = []; | |
280 var i = 0; | |
281 $(highlight_select_plot).each(function(){ | |
282 var color; | |
283 if (i < GeoTemConfig.datasets.length){ | |
284 var datasetColors = GeoTemConfig.getColor(i); | |
285 if (highlight_select_plot.length>GeoTemConfig.datasets.length) | |
286 color = "rgb("+datasetColors.r0+","+datasetColors.g0+","+datasetColors.b0+")"; | |
287 else | |
288 color = "rgb("+datasetColors.r1+","+datasetColors.g1+","+datasetColors.b1+")"; | |
289 } else { | |
290 var datasetColors = GeoTemConfig.getColor(i-GeoTemConfig.datasets.length); | |
291 color = "rgb("+datasetColors.r1+","+datasetColors.g1+","+datasetColors.b1+")"; | |
292 } | |
293 | |
294 highlight_select_plot_colors.push({ | |
295 color : color, | |
296 data : this | |
297 }); | |
298 i++; | |
299 }); | |
300 | |
301 density.plot = $.plot($(density.div), highlight_select_plot_colors, options); | |
302 density.parent.drawHandles(); | |
303 | |
304 var rangeBars = density.parent.rangeBars; | |
305 if (typeof rangeBars !== "undefined") | |
306 $(density.div).unbind("plothover", rangeBars.hoverFunction); | |
307 $(density.div).unbind("plothover", density.hoverFunction); | |
308 $(density.div).bind("plothover", density.hoverFunction); | |
309 | |
310 //this var prevents the execution of the plotclick event after a select event | |
311 density.wasSelection = false; | |
312 $(density.div).unbind("plotclick"); | |
313 $(density.div).bind("plotclick", density.clickFunction); | |
314 | |
315 $(density.div).unbind("plotselected"); | |
316 $(density.div).bind("plotselected", density.selectFuntion); | |
317 }, | |
318 | |
319 hoverFunction : function (event, pos, item) { | |
320 var hoverPoint; | |
321 //TODO: this could be wanted (if negative weight is used) | |
322 if ((item)&&(item.datapoint[1] != 0)) { | |
323 //at begin and end of plot there are added 0 points | |
324 hoverPoint = item.dataIndex-1; | |
325 } | |
326 //remember last point, so that we don't redraw the current state | |
327 //that "hoverPoint" may be undefined is on purpose | |
328 if (density.highlighted !== hoverPoint){ | |
329 density.highlighted = hoverPoint; | |
330 density.triggerHighlight(hoverPoint); | |
331 } | |
332 }, | |
333 | |
334 clickFunction : function (event, pos, item) { | |
335 if (density.wasSelection) | |
336 density.wasSelection = false; | |
337 else { | |
338 //remove selection handles (if there were any) | |
339 density.parent.clearHandles(); | |
340 | |
341 var selectPoint; | |
342 //that date may be undefined is on purpose | |
343 //TODO: ==0 could be wanted (if negative weight is used) | |
344 if ((item)&&(item.datapoint[1] != 0)) { | |
345 //at begin and end of plot there are added 0 points | |
346 selectPoint = item.dataIndex-1; | |
347 } | |
348 density.triggerSelection(selectPoint); | |
349 } | |
350 }, | |
351 | |
352 selectFuntion : function(event, ranges) { | |
353 var spanArray = density.parent.getSpanArray(density.singleTickWidth); | |
354 var startSpan, endSpan; | |
355 for (var i = 0; i < spanArray.length-1; i++){ | |
356 if ((typeof startSpan === "undefined") && (ranges.xaxis.from <= spanArray[i+1])) | |
357 startSpan = i; | |
358 if ((typeof endSpan === "undefined") && (ranges.xaxis.to <= spanArray[i+1])) | |
359 endSpan = i; | |
360 } | |
361 | |
362 if ((typeof startSpan !== "undefined") && (typeof endSpan !== "undefined")){ | |
363 density.triggerSelection(startSpan, endSpan); | |
364 density.wasSelection = true; | |
365 | |
366 density.parent.clearHandles(); | |
367 var xaxis = density.plot.getAxes().xaxis; | |
368 var x1 = density.plot.pointOffset({x:ranges.xaxis.from,y:0}).left; | |
369 var x2 = density.plot.pointOffset({x:ranges.xaxis.to,y:0}).left; | |
370 | |
371 density.parent.addHandle(x1,x2); | |
372 } | |
373 }, | |
374 | |
375 selectByX : function(x1, x2){ | |
376 density = this; | |
377 var xaxis = density.plot.getAxes().xaxis; | |
378 var offset = density.plot.getPlotOffset().left; | |
379 var from = xaxis.c2p(x1-offset); | |
380 var to = xaxis.c2p(x2-offset); | |
381 | |
382 var spanArray = density.parent.getSpanArray(density.singleTickWidth); | |
383 var startSpan, endSpan; | |
384 for (var i = 0; i < spanArray.length-1; i++){ | |
385 if ((typeof startSpan === "undefined") && (from <= spanArray[i+1])) | |
386 startSpan = i; | |
387 if ((typeof endSpan === "undefined") && (to <= spanArray[i+1])) | |
388 endSpan = i; | |
389 } | |
390 | |
391 if ((typeof startSpan !== "undefined") && (typeof endSpan !== "undefined")){ | |
392 density.triggerSelection(startSpan, endSpan); | |
393 } | |
394 }, | |
395 | |
396 drawDensityPlot : function(datasets, tickWidth) { | |
397 var density = this; | |
398 //calculate tick width (will be in ms) | |
399 delete density.tickCount; | |
400 delete density.singleTickWidth; | |
401 delete density.highlightedDatasetsPlot; | |
402 density.parent.zoomPlot(1); | |
403 if (typeof tickWidth !== "undefined"){ | |
404 density.singleTickWidth = tickWidth; | |
405 density.tickCount = Math.ceil((density.parent.overallMax-density.parent.overallMin)/tickWidth); | |
406 } | |
407 if ((typeof density.tickCount === "undefined") || (density.tickCount > density.maxTickCount)){ | |
408 density.tickCount = density.maxTickCount; | |
409 density.singleTickWidth = (density.parent.overallMax-density.parent.overallMin)/density.tickCount; | |
410 if (density.singleTickWidth === 0) | |
411 density.singleTickWidth = 1; | |
412 } | |
413 | |
414 var hashAndPlot = density.createUDData(datasets); | |
415 | |
416 density.datasetsPlot = hashAndPlot.plots; | |
417 density.datasetsHash = hashAndPlot.hashs; | |
418 | |
419 density.yValMin = 0; | |
420 density.yValMax = 0; | |
421 | |
422 density.combinedDatasetsPlot = []; | |
423 for (var i = 0; i < density.datasetsPlot.length; i++){ | |
424 for (var j = 0; j < density.datasetsPlot[i].length; j++){ | |
425 var val = density.datasetsPlot[i][j][1]; | |
426 | |
427 if (val < density.yValMin) | |
428 density.yValMin = val; | |
429 if (val > density.yValMax) | |
430 density.yValMax = val; | |
431 } | |
432 } | |
433 | |
434 density.showPlot(); | |
435 }, | |
436 | |
437 triggerHighlight : function(hoverPoint) { | |
438 var density = this; | |
439 var highlightedObjects = []; | |
440 | |
441 | |
442 if (typeof hoverPoint !== "undefined") { | |
443 $(density.datasetsHash).each(function(){ | |
444 if (typeof this[hoverPoint] !== "undefined") | |
445 highlightedObjects.push(this[hoverPoint]); | |
446 else | |
447 highlightedObjects.push([]); | |
448 }); | |
449 } else { | |
450 for (var i = 0; i < GeoTemConfig.datasets.length; i++) | |
451 highlightedObjects.push([]); | |
452 } | |
453 this.parent.core.triggerHighlight(highlightedObjects); | |
454 }, | |
455 | |
456 triggerSelection : function(startPoint, endPoint) { | |
457 var density = this; | |
458 var selection; | |
459 if (typeof startPoint !== "undefined") { | |
460 if (typeof endPoint === "undefined") | |
461 endPoint = startPoint; | |
462 density.selected = []; | |
463 $(density.datasetsHash).each(function(){ | |
464 var objects = []; | |
465 for (var i = startPoint; i <= endPoint; i++){ | |
466 $(this[i]).each(function(){ | |
467 if ($.inArray(this, objects) == -1){ | |
468 objects.push(this); | |
469 } | |
470 }); | |
471 } | |
472 density.selected.push(objects); | |
473 }); | |
474 | |
475 selection = new Selection(density.selected, density.parent); | |
476 } else { | |
477 //empty selection | |
478 density.selected = []; | |
479 for (var i = 0; i < GeoTemConfig.datasets.length; i++) | |
480 density.selected.push([]); | |
481 selection = new Selection(density.selected); | |
482 } | |
483 | |
484 this.parent.selectionChanged(selection); | |
485 this.parent.core.triggerSelection(selection); | |
486 }, | |
487 | |
488 highlightChanged : function(objects) { | |
489 if( !GeoTemConfig.highlightEvents ){ | |
490 return; | |
491 } | |
492 var density = this; | |
493 var emptyHighlight = true; | |
494 var selected_highlighted = objects; | |
495 if (typeof density.selected !== "undefined") | |
496 selected_highlighted = GeoTemConfig.mergeObjects(objects,density.selected); | |
497 $(selected_highlighted).each(function(){ | |
498 if ((this instanceof Array) && (this.length > 0)){ | |
499 emptyHighlight = false; | |
500 return false; | |
501 } | |
502 }); | |
503 if (emptyHighlight && (typeof density.selected === "undefined")){ | |
504 density.highlightedDatasetsPlot = []; | |
505 } else { | |
506 density.highlightedDatasetsPlot = density.createUDData(selected_highlighted).plots; | |
507 } | |
508 density.showPlot(); | |
509 }, | |
510 | |
511 selectionChanged : function(objects) { | |
512 if( !GeoTemConfig.selectionEvents ){ | |
513 return; | |
514 } | |
515 var density = this; | |
516 density.selected = objects; | |
517 density.highlightChanged([]); | |
518 }, | |
519 | |
520 deselection : function() { | |
521 }, | |
522 | |
523 filtering : function() { | |
524 }, | |
525 | |
526 inverseFiltering : function() { | |
527 }, | |
528 | |
529 triggerRefining : function() { | |
530 }, | |
531 | |
532 reset : function() { | |
533 }, | |
534 | |
535 show : function() { | |
536 }, | |
537 | |
538 hide : function() { | |
539 } | |
540 }; |