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 };