0
|
1 /*
|
|
2 * FuzzyTimelineRangeBars.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 FuzzyTimelineRangeBars
|
|
24 * Implementation for a fuzzy time-ranges barchart
|
|
25 * @author Sebastian Kruse (skruse@mpiwg-berlin.mpg.de)
|
|
26 *
|
|
27 * @param {HTML object} parent div to append the FuzzyTimeline
|
|
28 */
|
|
29 function FuzzyTimelineRangeBars(parent) {
|
|
30
|
|
31 this.rangeBars = this;
|
|
32
|
|
33 this.parent = parent;
|
|
34 this.options = parent.options;
|
|
35
|
|
36 this.datasets;
|
|
37 //contains selected data
|
|
38 this.selected = undefined;
|
|
39
|
|
40 this.datasetsPlot;
|
|
41 this.highlightedDatasetsPlot;
|
|
42 this.yValMin;
|
|
43 this.yValMax;
|
|
44 this.displayType;
|
|
45
|
|
46 this.plotDiv = this.parent.gui.plotDiv;
|
|
47
|
|
48 this.spanWidth;
|
|
49 this.tickSpans;
|
|
50 this.plot;
|
|
51 }
|
|
52
|
|
53 FuzzyTimelineRangeBars.prototype = {
|
|
54
|
|
55 initialize : function(datasets) {
|
|
56 var rangeBar = this;
|
|
57
|
|
58 rangeBar.datasets = datasets;
|
|
59 rangeBar.selected = [];
|
|
60 },
|
|
61
|
|
62 createPlot : function(datasets) {
|
|
63 var rangeBar = this;
|
|
64 var plots = [];
|
|
65 var objectHashes = [];
|
|
66
|
|
67 //-1 because last span is always empty (only there to have the ending date)
|
|
68 var tickCount = rangeBar.tickSpans.length-1;
|
|
69
|
|
70 $(datasets).each(function(){
|
|
71 var chartDataCounter = [];
|
|
72 var objectHash = new Object();
|
|
73
|
|
74 for (var i = 0; i < tickCount; i++){
|
|
75 chartDataCounter[i]=0;
|
|
76 }
|
|
77 //check if we got "real" datasets, or just array of objects
|
|
78 var datasetObjects = this;
|
|
79 if (typeof this.objects !== "undefined")
|
|
80 datasetObjects = this.objects;
|
|
81 $(datasetObjects).each(function(){
|
|
82 var ticks = rangeBar.parent.getTicks(this, rangeBar.spanWidth);
|
|
83 if (typeof ticks !== "undefined"){
|
|
84 var exactTickCount =
|
|
85 ticks.firstTickPercentage+
|
|
86 ticks.lastTickPercentage+
|
|
87 (ticks.lastTick-ticks.firstTick-1);
|
|
88 for (var i = ticks.firstTick; i <= ticks.lastTick; i++){
|
|
89 var weight = 0;
|
|
90 //calculate the weight for each span, that the object overlaps
|
|
91 if (rangeBar.parent.options.timelineMode == 'fuzzy'){
|
|
92 //in fuzzy mode, each span gets just a fraction of the complete weight
|
|
93 if (i == ticks.firstTick)
|
|
94 weight = this.weight * ticks.firstTickPercentage/exactTickCount;
|
|
95 else if (i == ticks.lastTick)
|
|
96 weight = this.weight * ticks.lastTickPercentage/exactTickCount;
|
|
97 else
|
|
98 weight = this.weight * 1/exactTickCount;
|
|
99 } else if (rangeBar.parent.options.timelineMode == 'stacking'){
|
|
100 //in stacking mode each span gets the same amount.
|
|
101 //(besides first and last..)
|
|
102 if (i == ticks.firstTick)
|
|
103 weight = this.weight * ticks.firstTickPercentage;
|
|
104 else if (i == ticks.lastTick)
|
|
105 weight = this.weight * ticks.lastTickPercentage;
|
|
106 else
|
|
107 weight = this.weight;
|
|
108
|
|
109 weight = this.weight;
|
|
110 }
|
|
111
|
|
112 chartDataCounter[i] += weight;
|
|
113 //add this object to the hash
|
|
114 if (typeof objectHash[i] === "undefined")
|
|
115 objectHash[i] = [];
|
|
116 objectHash[i].push(this);
|
|
117 }
|
|
118 }
|
|
119 });
|
|
120
|
|
121 //scale according to selected type
|
|
122 chartDataCounter = rangeBar.parent.scaleData(chartDataCounter);
|
|
123
|
|
124 //transform data so it can be passed to the flot barchart
|
|
125 var plotData = [];
|
|
126 for (var i = 0; i < tickCount; i++){
|
|
127 plotData[i] = [];
|
|
128 plotData[i][0] = i;
|
|
129 plotData[i][1] = chartDataCounter[i];
|
|
130 }
|
|
131
|
|
132 //delete bars with 0 values
|
|
133 for (var i = 0; i < tickCount; i++){
|
|
134 if (plotData[i][1]==0)
|
|
135 delete plotData[i];
|
|
136 }
|
|
137
|
|
138 plots.push(plotData);
|
|
139 objectHashes.push(objectHash);
|
|
140 });
|
|
141
|
|
142 return {plots:plots, hashs:objectHashes};
|
|
143 },
|
|
144
|
|
145 showPlot : function(){
|
|
146 var rangeBar = this;
|
|
147 var plot = rangeBar.datasetsPlot;
|
|
148 var highlight_select_plot = $.merge([],plot);
|
|
149
|
|
150 //see if there are selected/highlighted values
|
|
151 if (rangeBar.highlightedDatasetsPlot instanceof Array){
|
|
152 //check if plot is some other - external - graph
|
|
153 if (plot === rangeBar.datasetsPlot)
|
|
154 highlight_select_plot = $.merge(highlight_select_plot,rangeBar.highlightedDatasetsPlot);
|
|
155 }
|
|
156
|
|
157 var tickCount = rangeBar.tickSpans.length-1;
|
|
158 var ticks = [];
|
|
159
|
|
160 var axisFormatString = "YYYY";
|
|
161 if (rangeBar.spanWidth<60*1000){
|
|
162 axisFormatString = "YYYY/MM/DD HH:mm:ss";
|
|
163 } else if (rangeBar.spanWidth<60*60*1000) {
|
|
164 axisFormatString = "YYYY/MM/DD HH:mm";
|
|
165 } else if (rangeBar.spanWidth<24*60*60*1000){
|
|
166 axisFormatString = "YYYY/MM/DD HH";
|
|
167 } else if (rangeBar.spanWidth<31*24*60*60*1000){
|
|
168 axisFormatString = "YYYY/MM/DD";
|
|
169 } else if (rangeBar.spanWidth<12*31*24*60*60*1000){
|
|
170 axisFormatString = "YYYY/MM";
|
|
171 }
|
|
172 //only show ~10 labels on the x-Axis (increase if zoomed)
|
|
173 var labelModulo = Math.ceil(tickCount/(10*rangeBar.parent.zoomFactor));
|
|
174 for (var i = 0; i < tickCount; i++){
|
|
175 var tickLabel = "";
|
|
176 if (i%labelModulo==0){
|
|
177 tickLabel = rangeBar.tickSpans[i].format(axisFormatString);
|
|
178 }
|
|
179 while ((tickLabel.length > 1) && (tickLabel.indexOf("0")==0))
|
|
180 tickLabel = tickLabel.substring(1);
|
|
181 ticks[i] = [i,tickLabel];
|
|
182 }
|
|
183
|
|
184 var options = {
|
|
185 series:{
|
|
186 bars:{show: true}
|
|
187 },
|
|
188 grid: {
|
|
189 hoverable: true,
|
|
190 clickable: true,
|
|
191 backgroundColor: rangeBar.parent.options.backgroundColor,
|
|
192 borderWidth: 0,
|
|
193 minBorderMargin: 0,
|
|
194 },
|
|
195 xaxis: {
|
|
196 ticks: ticks,
|
|
197 min : 0,
|
|
198 max : tickCount,
|
|
199 },
|
|
200 yaxis: {
|
|
201 min : rangeBar.yValMin,
|
|
202 max : rangeBar.yValMax*1.05
|
|
203 },
|
|
204 tooltip: true,
|
|
205 tooltipOpts: {
|
|
206 content: function(label, xval, yval, flotItem){
|
|
207 var fromLabel = rangeBar.tickSpans[xval].format(axisFormatString);
|
|
208 while ((fromLabel.length > 1) && (fromLabel.indexOf("0")==0))
|
|
209 fromLabel = fromLabel.substring(1);
|
|
210 var toLabel = rangeBar.tickSpans[xval+1].clone().subtract("ms",1).format(axisFormatString);
|
|
211 while ((toLabel.length > 1) && (toLabel.indexOf("0")==0))
|
|
212 toLabel = toLabel.substring(1);
|
|
213 highlightString = fromLabel + " - " + toLabel + " : ";
|
|
214 //(max.)2 Nachkomma-Stellen von y-Wert anzeigen
|
|
215 highlightString += Math.round(yval*100)/100;
|
|
216
|
|
217 return highlightString;
|
|
218 }
|
|
219 },
|
|
220 selection: {
|
|
221 mode: "x"
|
|
222 }
|
|
223 };
|
|
224 if (!rangeBar.parent.options.showYAxis)
|
|
225 options.yaxis.show=false;
|
|
226
|
|
227 var highlight_select_plot_colors = [];
|
|
228 var i = 0;
|
|
229 $(highlight_select_plot).each(function(){
|
|
230 var color;
|
|
231 if (i < GeoTemConfig.datasets.length){
|
|
232 var datasetColors = GeoTemConfig.getColor(i);
|
|
233 if (highlight_select_plot.length>GeoTemConfig.datasets.length)
|
|
234 color = "rgb("+datasetColors.r0+","+datasetColors.g0+","+datasetColors.b0+")";
|
|
235 else
|
|
236 color = "rgb("+datasetColors.r1+","+datasetColors.g1+","+datasetColors.b1+")";
|
|
237 } else {
|
|
238 var datasetColors = GeoTemConfig.getColor(i-GeoTemConfig.datasets.length);
|
|
239 color = "rgb("+datasetColors.r1+","+datasetColors.g1+","+datasetColors.b1+")";
|
|
240 }
|
|
241
|
|
242 highlight_select_plot_colors.push({
|
|
243 color : color,
|
|
244 data : this
|
|
245 });
|
|
246 i++;
|
|
247 });
|
|
248
|
|
249 $(rangeBar.plotDiv).unbind();
|
|
250 rangeBar.plot = $.plot($(rangeBar.plotDiv), highlight_select_plot_colors, options);
|
|
251 rangeBar.parent.drawHandles();
|
|
252
|
|
253 var density = rangeBar.parent.density;
|
|
254 if (typeof density !== "undefined")
|
|
255 $(rangeBar.plotDiv).unbind("plothover", density.hoverFunction);
|
|
256 $(rangeBar.plotDiv).unbind("plothover", rangeBar.hoverFunction);
|
|
257 $(rangeBar.plotDiv).bind("plothover", $.proxy(rangeBar.hoverFunction,rangeBar));
|
|
258
|
|
259 //this var prevents the execution of the plotclick event after a select event
|
|
260 rangeBar.wasSelection = false;
|
|
261 $(rangeBar.plotDiv).unbind("plotclick");
|
|
262 $(rangeBar.plotDiv).bind("plotclick", $.proxy(rangeBar.clickFunction,rangeBar));
|
|
263
|
|
264 $(rangeBar.plotDiv).unbind("plotselected");
|
|
265 $(rangeBar.plotDiv).bind("plotselected", $.proxy(rangeBar.selectFunction,rangeBar));
|
|
266 },
|
|
267
|
|
268 hoverFunction : function (event, pos, item) {
|
|
269 var rangeBar = this;
|
|
270 var hoverBar;
|
|
271 var spans;
|
|
272 if (item) {
|
|
273 hoverBar = item.datapoint[0];
|
|
274 }
|
|
275 //remember last date, so that we don't redraw the current state
|
|
276 //that date may be undefined is on purpose
|
|
277 if (rangeBar.highlighted !== hoverBar){
|
|
278 rangeBar.highlighted = hoverBar;
|
|
279 if (typeof hoverBar === "undefined")
|
|
280 rangeBar.triggerHighlight();
|
|
281 else
|
|
282 rangeBar.triggerHighlight(hoverBar);
|
|
283 }
|
|
284 },
|
|
285
|
|
286 clickFunction : function (event, pos, item) {
|
|
287 var rangeBar = this;
|
|
288 if (rangeBar.wasSelection)
|
|
289 rangeBar.wasSelection = false;
|
|
290 else {
|
|
291 //remove selection handles (if there were any)
|
|
292 rangeBar.parent.clearHandles();
|
|
293
|
|
294 var clickBar;
|
|
295 if (item) {
|
|
296 //contains the x-value (date)
|
|
297 clickBar = item.datapoint[0];
|
|
298 }
|
|
299 if (typeof clickBar === "undefined")
|
|
300 rangeBar.triggerSelection();
|
|
301 else
|
|
302 rangeBar.triggerSelection(clickBar);
|
|
303 wasDataClick = true;
|
|
304 }
|
|
305 },
|
|
306
|
|
307 selectFunction : function(event, ranges) {
|
|
308 var rangeBar = this;
|
|
309 startBar = Math.floor(ranges.xaxis.from);
|
|
310 endBar = Math.floor(ranges.xaxis.to);
|
|
311 rangeBar.triggerSelection(startBar, endBar);
|
|
312 rangeBar.wasSelection = true;
|
|
313
|
|
314 rangeBar.parent.clearHandles();
|
|
315 var xaxis = rangeBar.plot.getAxes().xaxis;
|
|
316 var x1 = rangeBar.plot.pointOffset({x:ranges.xaxis.from,y:0}).left;
|
|
317 var x2 = rangeBar.plot.pointOffset({x:ranges.xaxis.to,y:0}).left;
|
|
318 rangeBar.parent.addHandle(x1,x2);
|
|
319 },
|
|
320
|
|
321 selectByX : function(x1, x2){
|
|
322 rangeBar = this;
|
|
323 var xaxis = rangeBar.plot.getAxes().xaxis;
|
|
324 var offset = rangeBar.plot.getPlotOffset().left;
|
|
325 var from = Math.floor(xaxis.c2p(x1-offset));
|
|
326 var to = Math.floor(xaxis.c2p(x2-offset));
|
|
327
|
|
328 rangeBar.triggerSelection(from, to);
|
|
329 },
|
|
330
|
|
331 drawRangeBarChart : function(datasets, spanWidth){
|
|
332 var rangeBar = this;
|
|
333 rangeBar.spanWidth = spanWidth;
|
|
334 rangeBar.tickSpans = rangeBar.parent.getSpanArray(rangeBar.spanWidth);
|
|
335 //-1 because last span is always empty (only there to have the ending date)
|
|
336 var tickCount = rangeBar.tickSpans.length-1;
|
|
337
|
|
338 if (tickCount > rangeBar.options.maxBars){
|
|
339 var zoomFactor = tickCount / rangeBar.options.maxBars;
|
|
340 rangeBar.parent.zoomPlot(zoomFactor);
|
|
341 } else
|
|
342 rangeBar.parent.zoomPlot(1);
|
|
343
|
|
344 rangeBar.yValMin = 0;
|
|
345 rangeBar.yValMax = 0;
|
|
346
|
|
347 var plotAndHash = rangeBar.createPlot(datasets);
|
|
348 rangeBar.datasetsPlot = plotAndHash.plots;
|
|
349 rangeBar.datasetsHash = plotAndHash.hashs;
|
|
350 delete rangeBar.highlightedDatasetsPlot;
|
|
351 //redraw selected plot to fit (possible) new scale
|
|
352 rangeBar.selectionChanged(rangeBar.selected);
|
|
353
|
|
354 //get min and max values
|
|
355 for (var i = 0; i < rangeBar.datasetsPlot.length; i++){
|
|
356 for (var j = 0; j < rangeBar.datasetsPlot[i].length; j++){
|
|
357 if (typeof rangeBar.datasetsPlot[i][j] !== "undefined"){
|
|
358 var val = rangeBar.datasetsPlot[i][j][1];
|
|
359
|
|
360 if (val < rangeBar.yValMin)
|
|
361 rangeBar.yValMin = val;
|
|
362 if (val > rangeBar.yValMax)
|
|
363 rangeBar.yValMax = val;
|
|
364 }
|
|
365 }
|
|
366 }
|
|
367
|
|
368 rangeBar.showPlot();
|
|
369 },
|
|
370
|
|
371 highlightChanged : function(objects) {
|
|
372 if( !GeoTemConfig.highlightEvents ){
|
|
373 return;
|
|
374 }
|
|
375 var rangeBar = this;
|
|
376 var emptyHighlight = true;
|
|
377 var selected_highlighted = objects;
|
|
378 if (typeof rangeBar.selected !== "undefined")
|
|
379 var selected_highlighted = GeoTemConfig.mergeObjects(objects,rangeBar.selected);
|
|
380 $(selected_highlighted).each(function(){
|
|
381 if ((this instanceof Array) && (this.length > 0)){
|
|
382 emptyHighlight = false;
|
|
383 return false;
|
|
384 }
|
|
385 });
|
|
386 if (emptyHighlight && (typeof rangeBar.selected === "undefined")){
|
|
387 rangeBar.highlightedDatasetsPlot = [];
|
|
388 } else {
|
|
389 rangeBar.highlightedDatasetsPlot = rangeBar.createPlot(selected_highlighted).plots;
|
|
390 }
|
|
391 rangeBar.showPlot();
|
|
392 },
|
|
393
|
|
394 selectionChanged : function(objects) {
|
|
395 if( !GeoTemConfig.selectionEvents ){
|
|
396 return;
|
|
397 }
|
|
398 var rangeBar = this;
|
|
399 rangeBar.selected = objects;
|
|
400 rangeBar.highlightChanged([]);
|
|
401 },
|
|
402
|
|
403 triggerHighlight : function(hoverPoint) {
|
|
404 var rangeBar = this;
|
|
405 var highlightedObjects = [];
|
|
406
|
|
407 if (typeof hoverPoint !== "undefined"){
|
|
408 $(rangeBar.datasetsHash).each(function(){
|
|
409 if (typeof this[hoverPoint] !== "undefined")
|
|
410 highlightedObjects.push(this[hoverPoint]);
|
|
411 else
|
|
412 highlightedObjects.push([]);
|
|
413 });
|
|
414 } else {
|
|
415 for (var i = 0; i < GeoTemConfig.datasets.length; i++)
|
|
416 highlightedObjects.push([]);
|
|
417 }
|
|
418
|
|
419 this.parent.core.triggerHighlight(highlightedObjects);
|
|
420 },
|
|
421
|
|
422 triggerSelection : function(startBar, endBar) {
|
|
423 var rangeBar = this;
|
|
424 var selection;
|
|
425 if (typeof startBar !== "undefined") {
|
|
426 if (typeof endBar === "undefined")
|
|
427 endBar = startBar;
|
|
428 rangeBar.selected = [];
|
|
429 $(rangeBar.datasetsHash).each(function(){
|
|
430 var objects = [];
|
|
431 for (var i = startBar; i <= endBar; i++){
|
|
432 $(this[i]).each(function(){
|
|
433 if ($.inArray(this, objects) == -1){
|
|
434 objects.push(this);
|
|
435 }
|
|
436 });
|
|
437 }
|
|
438 rangeBar.selected.push(objects);
|
|
439 });
|
|
440 selection = new Selection(rangeBar.selected, rangeBar.parent);
|
|
441 } else {
|
|
442 rangeBar.selected = [];
|
|
443 for (var i = 0; i < GeoTemConfig.datasets.length; i++)
|
|
444 rangeBar.selected.push([]);
|
|
445 selection = new Selection(rangeBar.selected);
|
|
446 }
|
|
447
|
|
448 rangeBar.parent.selectionChanged(selection);
|
|
449 rangeBar.parent.core.triggerSelection(selection);
|
|
450 },
|
|
451
|
|
452 deselection : function() {
|
|
453 },
|
|
454
|
|
455 filtering : function() {
|
|
456 },
|
|
457
|
|
458 inverseFiltering : function() {
|
|
459 },
|
|
460
|
|
461 triggerRefining : function() {
|
|
462 },
|
|
463
|
|
464 reset : function() {
|
|
465 },
|
|
466
|
|
467 show : function() {
|
|
468 },
|
|
469
|
|
470 hide : function() {
|
|
471 }
|
|
472 };
|