0
|
1 /*
|
|
2 * TimeDataSource.js
|
|
3 *
|
|
4 * Copyright (c) 2012, Stefan Jänicke. 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 TimeDataSource, TimeSlice, TimeStack
|
|
24 * implementation for aggregation of time items
|
|
25 * @author Stefan Jänicke (stjaenicke@informatik.uni-leipzig.de)
|
|
26 * @release 1.0
|
|
27 * @release date: 2012-07-27
|
|
28 * @version date: 2012-07-27
|
|
29 *
|
|
30 * @param {JSON} options time configuration
|
|
31 */
|
|
32 function TimeDataSource(options) {
|
|
33
|
|
34 this.options = options;
|
|
35 this.timeSlices = [];
|
|
36 this.unit
|
|
37 this.minDate
|
|
38 this.maxDate
|
|
39 this.eventSources
|
|
40 this.events
|
|
41 this.leftSlice
|
|
42 this.rightSlice
|
|
43
|
|
44 this.hashMapping
|
|
45
|
|
46 };
|
|
47
|
|
48 TimeDataSource.prototype = {
|
|
49
|
|
50 findTimeUnits : function(granularity, timeUnit, pixels) {
|
|
51
|
|
52 var time = SimileAjax.DateTime;
|
|
53 this.availableUnits = [];
|
|
54 var givenUnits = SimileAjax.DateTime.gregorianUnitLengths;
|
|
55 for (var i = 0; i < givenUnits.length; i++) {
|
|
56 if (granularity > i) {
|
|
57 continue;
|
|
58 }
|
|
59 var slices = 0;
|
|
60 var t = new Date(this.minDate.getTime());
|
|
61 do {
|
|
62 time.roundDownToInterval(t, i, undefined, 1, 0);
|
|
63 slices++;
|
|
64 time.incrementByInterval(t, i, undefined);
|
|
65 } while( t.getTime() <= this.maxDate.getTime() && slices < pixels+2 );
|
|
66 if (slices > 0 && slices <= pixels) {
|
|
67 this.availableUnits.push({
|
|
68 unit : i,
|
|
69 slices : slices,
|
|
70 label : SimileAjax.DateTime.Strings[GeoTemConfig.language][i]
|
|
71 });
|
|
72 }
|
|
73 }
|
|
74 var unitDiff200 = pixels + 1;
|
|
75 for (var i = 0; i < this.availableUnits.length; i++) {
|
|
76 var diff = Math.abs(this.availableUnits[i].slices - 200);
|
|
77 if (diff < unitDiff200) {
|
|
78 unitDiff200 = diff;
|
|
79 this.unit = this.availableUnits[i].unit;
|
|
80 }
|
|
81 }
|
|
82
|
|
83 },
|
|
84
|
|
85 getUnitIndex : function() {
|
|
86 for (var i = 0; i < this.availableUnits.length; i++) {
|
|
87 if (this.unit == this.availableUnits[i].unit) {
|
|
88 return i;
|
|
89 }
|
|
90 }
|
|
91 return 0;
|
|
92 },
|
|
93
|
|
94 setTimeUnit : function(unit) {
|
|
95 this.unit = unit;
|
|
96 this.initializeSlices();
|
|
97 },
|
|
98
|
|
99 /**
|
|
100 * initializes the TimeDataSource
|
|
101 * @param {Timeplot.ColumnSource[]} dataSources the column sources corresponding to the data sets
|
|
102 * @param {Timeplot.DefaultEventSource[]} eventSources the event sources corresponding to the column sources
|
|
103 * @param {TimeObject[][]} timeObjects an array of time objects of different sets
|
|
104 * @param {SimileAjax.DateTime} granularity the time granularity of the given data
|
|
105 */
|
|
106 initialize : function(dataSources, eventSources, timeObjects, granularity, timeUnit, pixels) {
|
|
107
|
|
108 this.dataSources = dataSources;
|
|
109 this.eventSources = eventSources;
|
|
110 this.timeObjects = timeObjects;
|
|
111
|
|
112 this.minDate = undefined;
|
|
113 this.maxDate = undefined;
|
|
114 this.hashMapping = [];
|
|
115 this.projHashMapping = [];
|
|
116
|
|
117 for (var i = 0; i < timeObjects.length; i++) {
|
|
118 this.hashMapping.push([]);
|
|
119 this.projHashMapping.push([]);
|
|
120 for (var j = 0; j < timeObjects[i].length; j++) {
|
|
121 var o = timeObjects[i][j];
|
|
122 if (o.isTemporal) {
|
|
123 var g = o.dates[this.options.timeIndex].granularity;
|
|
124 //o.getTimeGranularity(this.options.timeIndex);
|
|
125 if (g == null) {
|
|
126 continue;
|
|
127 }
|
|
128 var time = o.dates[this.options.timeIndex].date;
|
|
129 //o.getDate(this.options.timeIndex);
|
|
130 if (this.minDate == undefined || time.getTime() < this.minDate.getTime()) {
|
|
131 this.minDate = time;
|
|
132 }
|
|
133 if (this.maxDate == undefined || time.getTime() > this.maxDate.getTime()) {
|
|
134 this.maxDate = time;
|
|
135 }
|
|
136 }
|
|
137 }
|
|
138 }
|
|
139
|
|
140 if (this.minDate == undefined) {
|
|
141 this.minDate = this.options.defaultMinDate;
|
|
142 this.maxDate = this.options.defaultMaxDate;
|
|
143 }
|
|
144
|
|
145 this.findTimeUnits(granularity, timeUnit, pixels);
|
|
146 this.initializeSlices();
|
|
147
|
|
148 },
|
|
149
|
|
150 initializeSlices : function() {
|
|
151 for (var i = 0; i < this.dataSources.length; i++) {
|
|
152 this.dataSources[i]._range = {
|
|
153 earliestDate : null,
|
|
154 latestDate : null,
|
|
155 min : 0,
|
|
156 max : 0
|
|
157 };
|
|
158 }
|
|
159 this.timeSlices = [];
|
|
160 var time = SimileAjax.DateTime;
|
|
161 var t = new Date(this.minDate.getTime() - 0.9 * time.gregorianUnitLengths[this.unit]);
|
|
162 do {
|
|
163 time.roundDownToInterval(t, this.unit, undefined, 1, 0);
|
|
164 var slice = new TimeSlice(SimileAjax.NativeDateUnit.cloneValue(t), this.timeObjects.length, this.dataSources.length);
|
|
165 this.timeSlices.push(slice);
|
|
166 time.incrementByInterval(t, this.unit, undefined);
|
|
167 } while (t.getTime() <= this.maxDate.getTime() + 1.1 * time.gregorianUnitLengths[this.unit]);
|
|
168
|
|
169 for (var i = 0; i < this.timeObjects.length; i++) {
|
|
170 var projId = i;
|
|
171 if( this.dataSources.length == 1 ){
|
|
172 projId = 0;
|
|
173 }
|
|
174 for (var j = 0; j < this.timeObjects[i].length; j++) {
|
|
175 var o = this.timeObjects[i][j];
|
|
176 if (o.isTemporal) {
|
|
177 var date = o.dates[this.options.timeIndex].date;
|
|
178 //o.getDate(this.options.timeIndex);
|
|
179 for (var k = 0; k < this.timeSlices.length - 1; k++) {
|
|
180 var t1 = this.timeSlices[k].date.getTime();
|
|
181 var t2 = this.timeSlices[k + 1].date.getTime();
|
|
182 var stack = null, projStack = null;
|
|
183 if (date >= t1 && date < t2) {
|
|
184 stack = this.timeSlices[k].getStack(i);
|
|
185 projStack = this.timeSlices[k].getProjStack(projId);
|
|
186 }
|
|
187 if (k == this.timeSlices.length - 2 && date >= t2) {
|
|
188 stack = this.timeSlices[k + 1].getStack(i);
|
|
189 projStack = this.timeSlices[k + 1].getProjStack(projId);
|
|
190 }
|
|
191 if (stack != null) {
|
|
192 stack.addObject(o);
|
|
193 projStack.addObject(o);
|
|
194 this.hashMapping[i][o.index] = stack;
|
|
195 this.projHashMapping[i][o.index] = projStack;
|
|
196 break;
|
|
197 }
|
|
198 }
|
|
199 }
|
|
200 }
|
|
201 }
|
|
202
|
|
203 this.events = [];
|
|
204 for (var i = 0; i < this.eventSources.length; i++) {
|
|
205 var eventSet = [];
|
|
206 for (var j = 0; j < this.timeSlices.length; j++) {
|
|
207 var value = new Array("" + this.timeSlices[j].projStacks[i].value);
|
|
208 eventSet.push({
|
|
209 date : this.timeSlices[j].date,
|
|
210 value : value
|
|
211 });
|
|
212 }
|
|
213 this.eventSources[i].loadData(eventSet);
|
|
214 this.events.push(eventSet);
|
|
215 }
|
|
216
|
|
217 this.leftSlice = 0;
|
|
218 this.rightSlice = this.timeSlices.length - 1;
|
|
219
|
|
220 },
|
|
221
|
|
222 getSliceNumber : function() {
|
|
223 return this.timeSlices.length;
|
|
224 },
|
|
225
|
|
226 /**
|
|
227 * computes the slice index corresponding to a given time
|
|
228 * @param {Date} time the given time
|
|
229 * @return the corresponding slice index
|
|
230 */
|
|
231 getSliceIndex : function(time) {
|
|
232 for (var i = 0; i < this.timeSlices.length; i++) {
|
|
233 if (time == this.timeSlices[i].date) {
|
|
234 return i;
|
|
235 }
|
|
236 }
|
|
237 },
|
|
238
|
|
239 /**
|
|
240 * returns the time of a specific time slice
|
|
241 * @param {int} time the given slice index
|
|
242 * @return the corresponding slice date
|
|
243 */
|
|
244 getSliceTime : function(index) {
|
|
245 return this.timeSlices[index].date;
|
|
246 },
|
|
247
|
|
248 /**
|
|
249 * shifts the actual zoomed range
|
|
250 * @param {int} delta the value to shift (negative for left shift, positive for right shift)
|
|
251 * @return boolean value, if the range could be shifted
|
|
252 */
|
|
253 setShift : function(delta) {
|
|
254 if (delta == 1 && this.leftSlice != 0) {
|
|
255 this.leftSlice--;
|
|
256 this.rightSlice--;
|
|
257 return true;
|
|
258 } else if (delta == -1 && this.rightSlice != this.timeSlices.length - 1) {
|
|
259 this.leftSlice++;
|
|
260 this.rightSlice++;
|
|
261 return true;
|
|
262 } else {
|
|
263 return false;
|
|
264 }
|
|
265 },
|
|
266
|
|
267 /**
|
|
268 * zooms the actual range
|
|
269 * @param {int} delta the value to zoom (negative for zoom out, positive for zoom in)
|
|
270 * @param {Date} time the corresponding time of the actual mouse position on the plot
|
|
271 * @param {Date} leftTime the time of the left border of a selected timerange or null
|
|
272 * @param {Date} rightTime the time of the right border of a selected timerange or null
|
|
273 * @return boolean value, if the range could be zoomed
|
|
274 */
|
|
275 setZoom : function(delta, time, leftTime, rightTime) {
|
|
276 var n1 = 0;
|
|
277 var n2 = 0;
|
|
278 var m = -1;
|
|
279 if (delta > 0) {
|
|
280 m = 1;
|
|
281 if (leftTime != null) {
|
|
282 n1 = this.getSliceIndex(leftTime) - this.leftSlice;
|
|
283 n2 = this.rightSlice - this.getSliceIndex(rightTime);
|
|
284 } else {
|
|
285 slice = this.getSliceIndex(time);
|
|
286 if (slice == this.leftSlice || slice == this.rightSlice) {
|
|
287 return;
|
|
288 }
|
|
289 n1 = slice - 1 - this.leftSlice;
|
|
290 n2 = this.rightSlice - slice - 1;
|
|
291 }
|
|
292 } else if (delta < 0) {
|
|
293
|
|
294 n1 = this.leftSlice;
|
|
295 n2 = this.timeSlices.length - 1 - this.rightSlice;
|
|
296 }
|
|
297
|
|
298 var zoomSlices = 2 * delta;
|
|
299 if (Math.abs(n1 + n2) < Math.abs(zoomSlices)) {
|
|
300 zoomSlices = n1 + n2;
|
|
301 }
|
|
302
|
|
303 if (n1 + n2 == 0) {
|
|
304 return false;
|
|
305 }
|
|
306
|
|
307 var m1 = Math.round(n1 / (n1 + n2) * zoomSlices);
|
|
308 var m2 = zoomSlices - m1;
|
|
309
|
|
310 this.leftSlice += m1;
|
|
311 this.rightSlice -= m2;
|
|
312
|
|
313 return true;
|
|
314 },
|
|
315
|
|
316 /**
|
|
317 * resets the plots by loading data of actual zoomed range
|
|
318 */
|
|
319 reset : function(timeGeometry) {
|
|
320 for (var i = 0; i < this.eventSources.length; i++) {
|
|
321 this.eventSources[i].loadData(this.events[i].slice(this.leftSlice, this.rightSlice + 1));
|
|
322 if (i + 1 < this.eventSources.length) {
|
|
323 timeGeometry._earliestDate = null;
|
|
324 timeGeometry._latestDate = null;
|
|
325 }
|
|
326
|
|
327 }
|
|
328 },
|
|
329
|
|
330 /**
|
|
331 * Getter for actual zoom
|
|
332 * @return actual zoom value
|
|
333 */
|
|
334 getZoom : function() {
|
|
335 if (this.timeSlices == undefined) {
|
|
336 return 0;
|
|
337 }
|
|
338 return Math.round((this.timeSlices.length - 3) / 2) - Math.round((this.rightSlice - this.leftSlice - 2) / 2);
|
|
339 },
|
|
340
|
|
341 /**
|
|
342 * Getter for date of the first timeslice
|
|
343 * @return date of the first timeslice
|
|
344 */
|
|
345 earliest : function() {
|
|
346 return this.timeSlices[0].date;
|
|
347 },
|
|
348
|
|
349 /**
|
|
350 * Getter for date of the last timeslice
|
|
351 * @return date of the last timeslice
|
|
352 */
|
|
353 latest : function() {
|
|
354 return this.timeSlices[this.timeSlices.length - 1].date;
|
|
355 },
|
|
356
|
|
357 setOverlay : function(timeObjects) {
|
|
358 for (var i = 0; i < this.timeSlices.length; i++) {
|
|
359 this.timeSlices[i].reset();
|
|
360 }
|
|
361 for (var j in timeObjects ) {
|
|
362 for (var k in timeObjects[j] ) {
|
|
363 var o = timeObjects[j][k];
|
|
364 if (o.isTemporal) {
|
|
365 if (o.getTimeGranularity(this.options.timeIndex) == null) {
|
|
366 continue;
|
|
367 }
|
|
368 this.hashMapping[j][o.index].overlay += o.weight;
|
|
369 this.projHashMapping[j][o.index].overlay += o.weight;
|
|
370 }
|
|
371 }
|
|
372 }
|
|
373 },
|
|
374
|
|
375 size : function() {
|
|
376 if (this.timeSlices.length == 0) {
|
|
377 return 0;
|
|
378 }
|
|
379 return this.timeSlices[0].stacks.length;
|
|
380 }
|
|
381 };
|
|
382
|
|
383 /**
|
|
384 * small class that represents a time slice of the actual timeplot.
|
|
385 * it has a specific date and contains its corrsponding data objects as well
|
|
386 */
|
|
387 function TimeSlice(date, rows, projRows) {
|
|
388
|
|
389 this.date = date;
|
|
390 this.selected = false;
|
|
391
|
|
392 this.stacks = [];
|
|
393 this.projStacks = [];
|
|
394 for (var i = 0; i < rows; i++) {
|
|
395 this.stacks.push(new TimeStack());
|
|
396 }
|
|
397 for (var i = 0; i < projRows; i++) {
|
|
398 this.projStacks.push(new TimeStack());
|
|
399 }
|
|
400
|
|
401 this.getStack = function(row) {
|
|
402 return this.stacks[row];
|
|
403 };
|
|
404
|
|
405 this.getProjStack = function(row) {
|
|
406 return this.projStacks[row];
|
|
407 };
|
|
408
|
|
409 this.reset = function() {
|
|
410 for (var i in this.projStacks ) {
|
|
411 this.stacks[i].overlay = 0;
|
|
412 this.projStacks[i].overlay = 0;
|
|
413 }
|
|
414 };
|
|
415
|
|
416 this.overlay = function() {
|
|
417 var value = 0;
|
|
418 for (var i in this.projStacks ) {
|
|
419 if (this.projStacks[i].overlay > value) {
|
|
420 value = this.projStacks[i].overlay;
|
|
421 }
|
|
422 }
|
|
423 return value;
|
|
424 };
|
|
425
|
|
426 };
|
|
427
|
|
428 /**
|
|
429 * small class that represents a stack for a time slice which
|
|
430 * holds items for different datasets for the specific time range
|
|
431 */
|
|
432 function TimeStack() {
|
|
433
|
|
434 this.overlay = 0;
|
|
435 this.value = 0;
|
|
436 this.elements = [];
|
|
437
|
|
438 this.addObject = function(object) {
|
|
439 this.elements.push(object);
|
|
440 this.value += object.weight;
|
|
441 };
|
|
442
|
|
443 };
|