1 /*
  2  * Timemap.js Copyright 2010 Nick Rabinowitz.
  3  * Licensed under the MIT License (see LICENSE.txt)
  4  */
  5 
  6 /**
  7  * @fileOverview
  8  * Additional TimeMap manipulation functions.
  9  * Functions in this file are used to manipulate a TimeMap, TimeMapDataset, or
 10  * TimeMapItem after the initial load process.
 11  *
 12  * @author Nick Rabinowitz (www.nickrabinowitz.com)
 13  */
 14  
 15 (function(){
 16     var window = this,
 17         TimeMap = window.TimeMap, 
 18         TimeMapDataset = window.TimeMapDataset, 
 19         TimeMapItem = window.TimeMapItem,
 20         util = TimeMap.util;
 21         
 22 /*----------------------------------------------------------------------------
 23  * TimeMap manipulation: stuff affecting every dataset
 24  *---------------------------------------------------------------------------*/
 25 
 26 // XXX: This should $.extend the prototype, I think
 27  
 28 /**
 29  * Delete all datasets, clearing them from map and timeline. Note
 30  * that this is more efficient than calling clear() on each dataset.
 31  */
 32 TimeMap.prototype.clear = function() {
 33     var tm = this;
 34     tm.eachItem(function(item) {
 35         item.event = item.placemark = null;
 36     });
 37     tm.map.removeAllPolylines();
 38     tm.map.removeAllMarkers();
 39     tm.eventSource.clear();
 40     tm.datasets = [];
 41 };
 42 
 43 /**
 44  * Delete one dataset, clearing it from map and timeline
 45  *
 46  * @param {String} id    Id of dataset to delete
 47  */
 48 TimeMap.prototype.deleteDataset = function(id) {
 49     this.datasets[id].clear();
 50     delete this.datasets[id];
 51 };
 52 
 53 /**
 54  * Hides placemarks for a given dataset
 55  * 
 56  * @param {String} id   The id of the dataset to hide
 57  */
 58 TimeMap.prototype.hideDataset = function (id){
 59     if (id in this.datasets) {
 60         this.datasets[id].hide();
 61     }
 62 };
 63 
 64 /**
 65  * Hides all the datasets on the map
 66  */
 67 TimeMap.prototype.hideDatasets = function(){
 68     var tm = this;
 69     tm.each(function(ds) {
 70         ds.visible = false;
 71     });
 72     tm.filter("map");
 73     tm.filter("timeline");
 74 };
 75 
 76 /**
 77  * Shows placemarks for a given dataset
 78  * 
 79  * @param {String} id   The id of the dataset to hide
 80  */
 81 TimeMap.prototype.showDataset = function(id) {
 82     if (id in this.datasets) {
 83         this.datasets[id].show();
 84     }
 85 };
 86 
 87 /**
 88  * Shows all the datasets on the map
 89  */
 90 TimeMap.prototype.showDatasets = function() {
 91     var tm = this;
 92     tm.each(function(ds) {
 93         ds.visible = true;
 94     });
 95     tm.filter("map");
 96     tm.filter("timeline");
 97 };
 98  
 99 /**
100  * Change the default map type
101  *
102  * @param {String} mapType   The maptype for the map (see {@link TimeMap.mapTypes} for options)
103  */
104 TimeMap.prototype.changeMapType = function (mapType) {
105     var tm = this;
106     // check for no change
107     if (mapType == tm.opts.mapType) {
108         return;
109     }
110     // look for mapType
111     if (typeof(mapType) == 'string') {
112         mapType = TimeMap.mapTypes[mapType];
113     }
114     // no mapType specified
115     if (!mapType) {
116         return;
117     }
118     // change it
119     tm.opts.mapType = mapType;
120     tm.map.setMapType(mapType);
121 };
122 
123 /*----------------------------------------------------------------------------
124  * TimeMap manipulation: stuff affecting the timeline
125  *---------------------------------------------------------------------------*/
126 
127 /**
128  * Refresh the timeline, maintaining the current date
129  */
130 TimeMap.prototype.refreshTimeline = function () {
131     var topband = this.timeline.getBand(0);
132     var centerDate = topband.getCenterVisibleDate();
133     if (util.TimelineVersion() == "1.2") {
134         topband.getEventPainter().getLayout()._laidout = false;
135     }
136     this.timeline.layout();
137     topband.setCenterVisibleDate(centerDate);
138 };
139 
140 /**
141  * Change the intervals on the timeline.
142  *
143  * @param {String|Array} intervals   New intervals. If string, looks up in TimeMap.intervals.
144  */
145 TimeMap.prototype.changeTimeIntervals = function (intervals) {
146     var tm = this;
147     // check for no change or no intervals
148     if (!intervals || intervals == tm.opts.bandIntervals) {
149         return;
150     }
151     // resolve string references if necessary
152     intervals = util.lookup(intervals, TimeMap.intervals);
153     tm.opts.bandIntervals = intervals;
154     // internal function - change band interval
155     function changeInterval(band, interval) {
156         band.getEther()._interval = Timeline.DateTime.gregorianUnitLengths[interval];
157         band.getEtherPainter()._unit = interval;
158     }
159     // grab date
160     var topband = tm.timeline.getBand(0),
161         centerDate = topband.getCenterVisibleDate(),
162         x;
163     // change interval for each band
164     for (x=0; x<tm.timeline.getBandCount(); x++) {
165         changeInterval(tm.timeline.getBand(x), intervals[x]);
166     }
167     // re-layout timeline
168     if (topband.getEventPainter().getLayout) {
169         topband.getEventPainter().getLayout()._laidout = false;
170     }
171     tm.timeline.layout();
172     topband.setCenterVisibleDate(centerDate);
173 };
174 
175 
176 /*----------------------------------------------------------------------------
177  * TimeMapDataset manipulation: global settings, stuff affecting every item
178  *---------------------------------------------------------------------------*/
179 
180 /**
181  * Delete all items, clearing them from map and timeline
182  */
183 TimeMapDataset.prototype.clear = function() {
184     var ds = this;
185     ds.each(function(item) {
186         item.clear(true);
187     });
188     ds.items = [];
189     ds.timemap.timeline.layout();
190 };
191 
192 /**
193  * Delete one item, clearing it from map and timeline
194  * 
195  * @param {TimeMapItem} item      Item to delete
196  */
197 TimeMapDataset.prototype.deleteItem = function(item) {
198     var ds = this, x;
199     for (x=0; x < ds.items.length; x++) {
200         if (ds.items[x] == item) {
201             item.clear();
202             ds.items.splice(x, 1);
203             break;
204         }
205     }
206 };
207 
208 /**
209  * Show dataset
210  */
211 TimeMapDataset.prototype.show = function() {
212     var ds = this,
213         tm = ds.timemap;
214     if (!ds.visible) {
215         ds.visible = true;
216         tm.filter("map");
217         tm.filter("timeline");
218     }
219 };
220 
221 /**
222  * Hide dataset
223  */
224 TimeMapDataset.prototype.hide = function() {
225     var ds = this,
226         tm = ds.timemap;
227     if (ds.visible) {
228         ds.visible = false;
229         tm.filter("map");
230         tm.filter("timeline");
231     }
232 };
233 
234  /**
235  * Change the theme for every item in a dataset
236  *
237  * @param {TimeMapTheme|String} theme   New theme, or string key in {@link TimeMap.themes}
238  */
239  TimeMapDataset.prototype.changeTheme = function(newTheme) {
240     var ds = this;
241     newTheme = util.lookup(newTheme, TimeMap.themes);
242     ds.opts.theme = newTheme;
243     ds.each(function(item) {
244         item.changeTheme(newTheme, true);
245     });
246     ds.timemap.timeline.layout();
247  };
248  
249  
250 /*----------------------------------------------------------------------------
251  * TimeMapItem manipulation: manipulate events and placemarks
252  *---------------------------------------------------------------------------*/
253 
254 /** 
255  * Show event and placemark
256  */
257 TimeMapItem.prototype.show = function() {
258     var item = this;
259     item.showEvent();
260     item.showPlacemark();
261     item.visible = true;
262 };
263 
264 /** 
265  * Hide event and placemark
266  */
267 TimeMapItem.prototype.hide = function() {
268     var item = this;
269     item.hideEvent();
270     item.hidePlacemark();
271     item.visible = false;
272 };
273 
274 /**
275  * Delete placemark from map and event from timeline
276  * @param [suppressLayout]      Whether to suppress laying out the timeline 
277  *                              (e.g. for batch operations)
278  */
279 TimeMapItem.prototype.clear = function(suppressLayout) {
280     var item = this,
281         i;
282     // remove event
283     if (item.event) {
284         // this is just ridiculous
285         item.dataset.timemap.timeline.getBand(0)
286             .getEventSource()._events._events.remove(item.event);
287         if (!suppressLayout) {
288             item.timeline.layout();
289         }
290     }
291     // remove placemark
292     function removeOverlay(p) {
293         try {
294             if (item.getType() == 'marker') {
295                 item.map.removeMarker(p);
296             } 
297             else {
298                 item.map.removePolyline(p);
299             }
300         } catch(e) {}
301     }
302     if (item.placemark) {
303         item.hidePlacemark();
304         if (item.getType() == "array") {
305             item.placemark.forEach(removeOverlay);
306         } else {
307             removeOverlay(item.placemark);
308         }
309     }
310     item.event = item.placemark = null;
311 };
312 
313  /**
314  * Create a new event for the item.
315  * 
316  * @param {Date} start      Start date for the event
317  * @param {Date} [end]      End date for the event
318  */
319 TimeMapItem.prototype.createEvent = function(start, end) {
320     var item = this,
321         theme = item.opts.theme,
322         instant = (end === undefined),
323         title = item.getTitle();
324     // create event
325     var event = new Timeline.DefaultEventSource.Event(start, end, null, null, instant, title, 
326         null, null, null, theme.eventIcon, theme.eventColor, null);
327     // add references
328     event.item = item;
329     item.event = event;
330     item.dataset.eventSource.add(event);
331     item.timeline.layout();
332 };
333  
334  /**
335  * Change the theme for an item
336  *
337  * @param {TimeMapTheme|String} theme   New theme, or string key in {@link TimeMap.themes}
338  * @param [suppressLayout]      Whether to suppress laying out the timeline 
339  *                              (e.g. for batch operations)
340  */
341  TimeMapItem.prototype.changeTheme = function(newTheme, suppressLayout) {
342     var item = this,
343         type = item.getType(),
344         event = item.event,
345         placemark = item.placemark,
346         i;
347     newTheme = util.lookup(newTheme, TimeMap.themes);
348     item.opts.theme = newTheme;
349     // internal function - takes type, placemark
350     function changePlacemark(pm) {
351         pm.addData(newTheme);
352         // XXX: Need to update this in Mapstraction - most implementations not available
353         pm.update();
354     }
355     // change placemark
356     if (placemark) {
357         if (type == 'array') {
358             placemark.forEach(changePlacemark);
359         } else {
360             changePlacemark(placemark);
361         }
362     }
363     // change event
364     if (event) {
365         event._color = newTheme.eventColor;
366         event._icon = newTheme.eventIcon;
367         if (!suppressLayout) {
368             item.timeline.layout();
369         }
370     }
371 };
372 
373 
374 /** 
375  * Find the next or previous item chronologically
376  *
377  * @param {Boolean} [backwards=false]   Whether to look backwards (i.e. find previous) 
378  * @param {Boolean} [inDataset=false]   Whether to only look in this item's dataset
379  * @return {TimeMapItem}                Next/previous item, if any
380  */
381 TimeMapItem.prototype.getNextPrev = function(backwards, inDataset) {
382     var item = this,
383         eventSource = item.dataset.timemap.timeline.getBand(0).getEventSource(),
384         // iterator dates are non-inclusive, hence the juggle here
385         i = backwards ? 
386             eventSource.getEventReverseIterator(
387                 new Date(eventSource.getEarliestDate().getTime() - 1),
388                 item.event.getStart()) :
389             eventSource.getEventIterator(
390                 item.event.getStart(), 
391                 new Date(eventSource.getLatestDate().getTime() + 1)
392             ),
393         next = null;
394     if (!item.event) {
395         return;
396     }
397     while (next === null) {
398         if (i.hasNext()) {
399             next = i.next().item;
400             if (inDataset && next.dataset != item.dataset) {
401                 next = null;
402             }
403         } else {
404             break;
405         }
406     }
407     return next;
408 };
409 
410 /** 
411  * Find the next item chronologically
412  *
413  * @param {Boolean} [inDataset=false]   Whether to only look in this item's dataset
414  * @return {TimeMapItem}                Next item, if any
415  */
416 TimeMapItem.prototype.getNext = function(inDataset) {
417     return this.getNextPrev(false, inDataset);
418 };
419 
420 /** 
421  * Find the previous item chronologically
422  *
423  * @requires Timeline v.2.2.0 or greater
424  *
425  * @param {Boolean} [inDataset=false]   Whether to only look in this item's dataset
426  * @return {TimeMapItem}                Next item, if any
427  */
428 TimeMapItem.prototype.getPrev = function(inDataset) {
429     return this.getNextPrev(true, inDataset);
430 };
431 
432 })();