emsApplication/applications/WebConfigure/web/js/lib/timeglider/TG_TimelineView.js

4354 lines
108 KiB
JavaScript
Raw Normal View History

2024-05-24 12:19:45 +08:00
/*
* Timeglider for Javascript / jQuery
* http://timeglider.com/jquery
*
* Copyright 2013, Mnemograph LLC
* Licensed under Timeglider Dual License
* http://timeglider.com/jquery/?p=license
*
*/
/*
****************************************
timeglider.TimelineView
****************************************
*/
(function(tg){
// MED below is a reference to the mediator reference
// that will be passed into the main Constructor below
var TG_Date = tg.TG_Date,
PL = "",
MED = "",
options = {},
ticksSpeed = 0,
t1Left = 0,
t2Left = 0,
ticksSpeedIv,
container_name = '',
$ = jQuery,
intervals ={},
WIDGET_ID = "",
CONTAINER, TICKS, DATE, FOCUS_DATE,
CLICKORTOUCH = $.support.touch ? "touchstart": "click";
var stripPx = function (somethingPx) {
if (typeof somethingPx == "number") return somethingPx;
if (!somethingPx) return 0;
return parseInt(somethingPx.replace("px", ""), 10);
}
/*
* timeglider.TG_TimelineView
* This is _not_ a backbone view, though
* other elements inside of it are.
*
*
*/
tg.TG_TimelinePlayer = function (widget, mediator) {
var me = this;
// this.MED = mediator;
// vars declared in closure above
MED = mediator;
// timeline | list
MED.viewMode = "timeline";
options = mediator.options;
// core identifier to "uniquify" the container
PL = "#" + widget._id;
WIDGET_ID = widget._id;
container_name = options.base_namespace + "#" + WIDGET_ID;
this.titleBar = true;
this.singleTitleHeight = 0;
MED.setImageLaneHeight(options.image_lane_height, false, true);
$("#test2").bind('click',function()
{
var date_str="2015-8-22 5:54:00";
MED.gotoDateZoom(date_str);
});
/* references specific to the instance (rather than timeglider) so
one can have more than one instance of the widget on a page */
this._views = {
PLACE:PL,
CONTAINER : PL + " .timeglider-container",
SCRIM : PL + " .tg-scrim",
DATE : PL + " .tg-footer-center",
HEADER : PL + " .tg-widget-header",
FOCUS_DATE : PL + " .tg-date-display",
TIMELINE_MENU : PL + " .timeglider-timeline-menu",
TIMELINE_MENU_UL : PL + " .timeglider-timeline-menu ul",
TIMELINE_LIST_BT : PL + " .timeglider-list-bt",
SLIDER_CONTAINER : PL + " .timeglider-slider-container",
SLIDER : PL + " .timeglider-slider",
ZOOM_DISPLAY : PL + " .timeglider-zoomlevel-display",
TRUCK : PL + " .timeglider-truck",
CENTERLINE : PL + " .timeglider-centerline",
TICKS : PL + " .timeglider-ticks",
HANDLE : PL + " .timeglider-handle",
FOOTER : PL + " .timeglider-footer",
FILTER_BT : PL + " .timeglider-filter-bt",
FILTER_BOX : PL + " .timeglider-filter-box",
SETTINGS_BT : PL + " .timeglider-settings-bt"
}
// shorthand for common elements
CONTAINER = this._views.CONTAINER;
TICKS = this._views.TICKS;
this.dragSpeed = 0;
this.tickNum = 0;
this.leftside = 0;
this.rightside = 0;
this.ticksHandleOffset = 0;
this.timeoout_id = 1;
this.sliderActive = false;
this.ztop = 1000;
this.filterBoxActivated = false;
// this needs to be less than or equal to
// timeglider.css value for .timeglider-tick
// height property
this.tick_height = 32;
// a state var for the left-right position of the timeline
// to help track whether the timeline is too far left/right
// DEPRECATED 12 March 2013
// in favor of draggable containment array
//this.dragScopeState = {state:"okay",pos:0};
/* TEMPLATES FOR THINGS LIKE MODAL WINDOWS
* events themselves are non-templated and rendered in TG_Org.js
* as there are too many on-the-fly style attributes etc, and
* the current theory is that templating would create lag
*
*
*/
// in case custom event_modal fails, we need this object to exist
this._templates = {}
this._templates = {
// allows for customized templates imported
test : "testola",
event_modal_small: "<div class='tg-modal timeglider-ev-modal ui-widget-content ${extra_class}' id='${id}_modal'>"
+ "<div class='tg-close-button tg-close-button-remove'></div>"
+ "<div class='dateline'>{{html dateline}}</div>"
+ "<h4 id='title'>${title}</h4>"
+ "<div class='tg-ev-modal-description jscroll'><p>{{html image}}{{html description}}</p></div>"
+ "<ul class='timeglider-ev-modal-links'>{{html links}}</ul>"
+ "</div>",
// For displaying an exterior page directly in the modal
event_modal_iframe: "<div class='tg-modal timeglider-ev-modal ui-widget-content tg-iframe-modal' id='${id}_modal'>"
+ "<div class='tg-close-button tg-close-button-remove'></div>"
+ "<div class='dateline'>{{html dateline}}</div>"
+ "<h4 id='title'>{{html title}}</h4>"
+ "<iframe frameborder='none' src='${link}'></iframe>"
+ "</div>",
// generated, appended on the fly, then removed
event_modal_full : $.template( null,
////////
"<div class='tg-modal tg-full_modal' id='ev_${id}_modal'>"
+ "<div class='tg-full_modal_scrim'></div>"
+ "<div class='tg-full_modal_panel'>"
+ "<div class='tg-close-button tg-full_modal_close'></div>"
+ "<div class='tg-full_modal_content'>"
+ "<div class='dateline'>{{html dateline}}</div>"
+ "<h4>${title}</h4><div class='tg-full_modal-vidimg'></div>"
+ "<div class='tg-full_modal-body'>"
+ "{{html image}}{{html description}}"
// + "<div id='insert'></div>"
+ "</div>"
+ "<div class='tg-full_modal-links'><ul>{{html links}}</ul></div>"
// end of modal
+ "</div>"),
// generated, appended on the fly, then removed
filter_modal : $.template( null,
"<div class='tg-modal timeglider-filter-box'>"+
"<div class='tg-close-button'></div>"+
"<h3>search/filter</h3>"+
"<div class='timeglider-menu-modal-content'>"+
"<div class='timeglider-formline'>"+
"<input placeholder='keyword(s)' type='text' class='timeglider-filter-search'></div>"+
"<div class='tg-filter-cbs'>"+
"<input type='checkbox' id='filter_t' checked><label for='filter_t'>title</label>"+
"&nbsp;&nbsp;&nbsp;<input type='checkbox' id='filter_d'><label for='filter_d'>description</label>"+
"</div>"+
"<div class='timeglider-formline filter-tags'>"+
"<input type='text' id='filter-tags' class='timeglider-filter-tags'>"+
"</div>"+
// "<div class='timeglider-formline'>hide: "+
// "<input type='text' class='timeglider-filter-exclude'></div>"+
"<ul class='buttons'>"+
"<li class='timeglider-filter-apply'>go</li>"+
"<li class='timeglider-filter-clear'>clear</li>"+
"</ul></div>"+
"<div class='tg-modal-corner tg-modal-corner-south'>"+
"</div>"),
timeline_list_modal : $.template( null,
"<div class='tg-modal timeglider-timeline-menu'>"+
"<div class='tg-close-button'></div>"+
"<h3>timelines</h3>"+
"<div class='timeglider-menu-modal-content'><ul></ul></div>"+
"<div class='tg-modal-corner tg-modal-corner-south'>"+
"</div>"),
settings_modal : $.template( null,
"<div class='tg-modal timeglider-settings-modal'>"+
"<div class='tg-close-button'></div>"+
"<h3>settings</h3>"+
"<div class='timeglider-menu-modal-content'>"+
"<div class='timeglider-settings-timezone'></div></div>"+
"<div class='tg-modal-corner tg-modal-corner-south'>"+
"</div>"),
legend_modal : $.template( null,
"<div class='timeglider-menu-modal tg-legend tg-display-none' id='${id}'>"+
"<div class='tg-close-button-small tg-legend-close'></div>"+
"<div class='timeglider-menu-modal-content'><ul id='${id}'>{{html legend_list}}</ul>"+
"<div class='tg-legend-controls'><span class='tg-legend-all'>all</span><span class='tg-legend-none'> | none</span></div>"+
"</div>"+
"</div>")
};
this.timelineInfoModal = Backbone.View.extend({
tagName: "div",
model:tg.TG_Timeline,
className: 'tg-modal tg-timeline-modal ui-widget-content',
events: {
"click .tg-close": "remove",
"click .tg-timeline-start": "timelineStart",
"click .tg-modal-tags": "openTagsInfo"
},
// "tags":{"mardigras":2,"chris":2,"arizona":2,"netscape":2,"flop":1},
template: function () {
var tags1 = "", tags_intro = "", tags2 = "", thtm = [];
var tl_tags = this.model.get("tags");
// if tags
if (_.size(tl_tags) > 0) {
tags1 = "<li class='tg-modal-tags'></li>";
_.each(tl_tags, function(val, key) {
thtm.push(key + " (" + val + ")");
});
tags_intro = "<p class='tags-intro'>Use the search tool (at lower right) to filter this timeline according to these tags:</p>";
tags2 = "<div class='tg-modal-tags-info'>" + tags_intro + thtm.join(", ") + "</div>"
}
return "<h4>${title}</h4>"
+ "<div class='tg-close tg-close-button'></div>"
+ "<div class='tg-timeline-description jscroll'>{{html description}}</div>"
+ "<ul>" + tags1 + "<li data-timeline_id='" + this.model.get("id") + "' class='tg-timeline-start'>start</li></ul>" + tags2
+ "<div class='tg-modal-corner tg-modal-corner-north'></div>";
},
openTagsInfo: function() {
var $ti = $(this.el).find(".tg-modal-tags-info");
if (!$ti.is(":visible")) {
$(this.el).find(".tg-modal-tags-info").slideDown();
} else {
$(this.el).find(".tg-modal-tags-info").slideUp();
}
},
timelineStart: function() {
MED.focusTimeline(this.model.get("id"));
this.remove();
},
initialize: function() {
// this.model.bind('change', this.render, this);
},
render: function() {
$(this.el).html($.tmpl(this.template(), this.model.attributes)).attr("id", this.model.get("id") + "_timelineInfoModal");
return this;
},
remove: function() {
$(this.el).fadeOut();
}
});
this.presInfoModal = Backbone.View.extend({
tagName: "div",
model:tg.TG_Timeline,
className: 'tg-modal tg-timeline-modal tg-pres-modal ui-widget-content',
events: {
"click .tg-close": "remove",
"click .tg-pres-start": "presStart"
},
template: function () {
return "<div class='tg-timeline-description jscroll'>{{html description}}</div>"
+ "<ul><li class='tg-close'>close</li><li class='tg-pres-start'>start</li></ul>"
+ "<div class='tg-modal-corner tg-modal-corner-north'></div>";
},
presStart: function() {
var pres = MED.presentation;
MED.gotoDateZoom(pres.focus_date.dateStr, pres.initial_zoom);
},
render: function() {
$(this.el).html($.tmpl(this.template(), this.model)).attr("id", "presInfoModal");
return this;
},
remove: function() {
$(this.el).remove();
}
});
this.datepicker = Backbone.View.extend({
tagName: "div",
className: 'tg-modal tg-datepicker',
events: {
"click .tg-close-button": "remove",
"click .goto-save": "gotoDate",
"keydown .dateinput": "doKeydown"
},
template: function () {
var focus = MED.getFocusDate();
var val = focus.dateStr.split(" ")[0];
return "<div class='tg-close-button'></div>"
+ "<h3>Go to...</h3>"
+ "<div class='timeglider-menu-modal-content'>"
+ "<div class='tg-dtinput-wrap' id='goto-wrap'> "
+ "<input class='mousetrap dateinput' type='text' id='goto' value='" + val + "'>"
+ "<div class='goto-save save button' id='goto-go'>go</div>"
+ "<div class='cal_icon'></div>"
+ "</div>"
+ "</div>";
},
doKeydown: function(e) {
switch(e.which) {
case 0: case 9: case 13:
this.gotoDate();
break;
}
},
gotoDate: function() {
var date_str = $(this.el).find("input.dateinput").val();
MED.gotoDateZoom(date_str);
this.remove();
},
render: function() {
$(this.el).html($.tmpl(this.template(), this.model)).attr("id", "datepickerModal");
$(this.el).find("#goto-wrap").timegliderDatePicker({
is_touch_device:false,
position:{
my: "left bottom",
at: "left top",
// skip instance; it will be defined by datePicker
collision:"none"
}
});
return this;
},
remove: function() {
// $(this.el).fadeOut();
$(this.el).remove();
me.datepickerOpen = false;
}
});
$(CONTAINER)
.delegate(".timeline-info-bt", CLICKORTOUCH, function () {
var id = $(this).data("timeline_id");
me.timelineModal(id);
})
.delegate(".tg-expcol-bt", CLICKORTOUCH, function () {
var id = $(this).data("timeline_id");
me.expandCollapseTimeline(id);
})
.delegate(".tg-invert-bt", CLICKORTOUCH, function () {
var id = $(this).data("timeline_id");
me.invertTimeline(id);
})
.delegate(".tg-legend-bt", CLICKORTOUCH, function () {
var id = $(this).data("timeline_id");
me.legendModal(id);
})
.delegate(".tg-close-button-remove", CLICKORTOUCH, function () {
$(this).parent().remove()
})
.delegate(".tg-full_modal_scrim, .tg-full_modal_close", CLICKORTOUCH, function () {
$(".tg-full_modal").remove();
})
.delegate(".tg-event-overflow", CLICKORTOUCH, function () {
MED.zoom(-1);
})
.delegate(".tg-event-overflow", "hover", function (e) {
// var evid = $(this).data("event_id");
//!TODO
// take id and focus to it, then zoom in until it's
// visible: then highlight and fade out highlight
})
.delegate(".tg-legend-close", CLICKORTOUCH, function () {
var $legend = $(CONTAINER + " .tg-legend");
$legend.fadeOut(300, function () { $legend.remove(); });
MED.setFilters({origin:"legend", icon: "all"}, []);
})
.delegate(".tg-legend-all", CLICKORTOUCH, function () {
var $panel = $(this).closest(".tg-legend");
var panel_id = $panel.attr("id");
var icons = [];
if (panel_id == "pres" || timeglider.mode == "presentation") {
var pres_legend = MED.presentation.legend;
icons = _.pluck(pres_legend, "icon");
} else {
var tl = MED.timelineCollection.get(panel_id);
icons = _.pluck(tl.get("legend"), "icon");
}
if (options.legend.type === "checkboxes") {
MED.setFilters({origin:"legend", icon: "provided"}, icons);
$(CONTAINER + " .tg-legend li").each(function () {
$(this).addClass("tg-legend-icon-selected");
});
} else {
MED.setFilters({origin:"legend", icon: "all"});
$(CONTAINER + " .tg-legend li").each(function () {
$(this).removeClass("tg-legend-icon-selected");
});
}
})
// optional none button
.delegate(".tg-legend-none", CLICKORTOUCH, function () {
$(CONTAINER + " .tg-legend li").each(function () {
$(this).removeClass("tg-legend-icon-selected");
});
MED.setFilters({origin:"legend", icon: "none"});
})
.delegate(".tg-timeline-start", CLICKORTOUCH, function() {
var tid = $(this).data("timeline_id");
MED.focusTimeline(tid);
})
.delegate(".tg-prev", CLICKORTOUCH, function() {
MED.gotoPreviousEvent(true);
})
.delegate(".tg-next", CLICKORTOUCH, function() {
MED.gotoNextEvent(true);
})
.delegate(".timeglider-tick", CLICKORTOUCH, function(e) {
var loc = MED.getDateFromOffset(e.pageX);
MED.gotoDateZoom(loc.dateStr);
})
.delegate(".tg-pres-start", CLICKORTOUCH, function() {
me.startPresentation();
})
.delegate(".pres-info-bt", CLICKORTOUCH, function () {
me.presentationModal();
})
.delegate(".tg-pres-header h2", CLICKORTOUCH, function () {
var tid = $(this).data("timeline_id");
if (tid == "primary") {
me.startPresentation();
me.presentationModal();
} else {
alert("Drill baby, drill!");
}
})
.delegate(".tg-title-prev-events", CLICKORTOUCH, function() {
MED.gotoPreviousEvent(true);
})
.delegate(".tg-title-next-events", CLICKORTOUCH, function() {
MED.gotoNextEvent(true);
})
.css("height", $(PL).height());
$(".tg-zoom-in").bind(CLICKORTOUCH, function() {
MED.zoom(-1);
});
$(".tg-zoom-out").bind(CLICKORTOUCH, function() {
MED.zoom(1);
});
$(this._views.FOCUS_DATE).bind(CLICKORTOUCH, function() {
me.datepickerModal();
});
$(window).resize(_.throttle(function() {
MED.resize();
}, 700));
// END CONTAINER CHAIN
MED.base_font_size = options.base_font_size;
if (options.show_footer == false) {
$(this._views.FOOTER).css("display", "none");
}
this.dimensions = MED.dimensions = this.getWidgetDimensions();
// distance from bottom of container (not vertically from ticks)
// for timelines to be by default; but if a timeline has a "top" value,
// it's position will be set according to that
this.initTimelineVOffset = this.dimensions.container.height - (this.dimensions.footer.height + this.dimensions.tick.height + 18);
// INITIAL CONSTRUCTION
this.buildSlider();
this.setupScroller();
this.setPanButton($(".timeglider-pan-right"),-30);
this.setPanButton($(".timeglider-pan-left"),30);
$(this._views.TRUCK)
// doubleclicking will be used by authoring mode
.bind('dblclick', function(e) {
MED.registerUIEvent({name:"dblclick", event:e});
})
.bind('mousewheel', function(event, delta) {
var vec = (delta < 0) ? Math.floor(delta): Math.ceil(delta);
var dir = -1 * vec;
MED.mousewheelChange(dir);
if (options.mousewheel !== "none") {
// prevent default browser scrolling
return false;
}
}); // end TRUCK EVENTS
function registerTicksSpeed () {
//!TODO: for gliding
}
$(TICKS)
.draggable({ axis: 'x',
start: function(event, ui) {
me.eventUnHover();
},
// will be overridden later
containment: [-20000,0,20000,0],
cancel:".tg-modal",
drag: function(event, ui) {
t1Left = Math.floor($(this).position().left);
MED.setTicksOffset(t1Left);
ticksSpeed = t1Left - t2Left;
t2Left = t1Left;
return true;
},
stop: function(event, ui) {
me.resetTicksHandle();
me.registerDragging();
me.registerTitles();
}
})
// TODO: make function displayCenterline()
// TODO: simply append a centerline template rather than .css'ing it!
me.resizeElements();
/* PUB-SUB "LISTENERS" SUBSCRIBERS */
$.subscribe(container_name + ".mediator.ticksOffsetChange", function () {
me.tickHangies();
me.registerDragging();
me.registerTitles();
});
$.subscribe(container_name + ".viewer.rendered", function () {
// change scroller
});
$.subscribe(container_name + ".mediator.mousewheelChange", function (info) {
if (info.action === "pan") {
me.pan(info.dir * 10);
me.throttledResetTicksHandle();
}
});
$.subscribe(container_name + ".mediator.focusToEvent", function () {
// mediator takes care of focusing date
var ev = MED.focusedEvent;
});
$.subscribe(container_name + ".mediator.imageLaneHeightSetUi", function () {
me.setImageLaneHandle();
});
$.subscribe(container_name + ".mediator.zoomLevelChange", function () {
me.tickNum = 0;
me.leftside = 0;
var zl = MED.getZoomLevel();
// if the slider isn't already at the given value change in
$(me._views.SLIDER).slider("value", me.invSliderVal(zl));
me.displayZoomLevel(zl);
me.castTicks("zoomLevelChange");
});
$.subscribe(container_name + ".mediator.initialScope", function () {
var iscope = MED.getInitialScope(),
ls = iscope.left_sec,
rs = iscope.right_sec,
spp = iscope.spp,
c_pos = $(CONTAINER).offset(),
c_width = $(CONTAINER).width(),
lshift = ls - iscope.timelineBounds.first,
rshift = iscope.timelineBounds.last - rs,
// how far to drag right??
r_extreme = (Math.floor(lshift/spp) + c_pos.left) + (c_width/2),
// how far to drag left??;
l_extreme = (( -1 * Math.ceil(rshift/spp)) + c_pos.left) - (c_width/2);
if (options.constrain_to_data && MED.activeTimelines.length > 0) {
$(TICKS).draggable("option", "containment", [l_extreme, 0, r_extreme, 0]);
}
});
/// This happens on a TOTAL REFRESH of
/// ticks, as when zooming; panning will load
/// events of active timelines per tick
$.subscribe(container_name + ".mediator.ticksReadySignal", function (b) {
if (MED.ticksReady === true) {
me.freshTimelines();
}
});
/*
Renews the timeline at current focus/zoom, but with
possibly different timeline/legend/etc parameters
! The only view method that responds directly to a model refresh()
*/
$.subscribe(container_name + ".mediator.refreshSignal", function () {
me.tickNum = 0;
me.leftside = 0;
me.castTicks("refreshSignal");
});
// adding to or removing from ticksArray
// DORMANT: necessary?
$.subscribe(container_name + ".mediator.ticksArrayChange", function () {
// empty for now
});
$.subscribe(container_name + ".mediator.scopeChange", function() {
var scope = MED.getScope();
var tbounds = scope.timelineBounds;
var focus = scope.focusDateSec;
/*
// DEPRECATED 12 March 2013
// in favor of draggable containment array
if (focus > tbounds.last) {
// past right end of timeline(s): stop leftward drag
me.dragScopeState = {state:"over-right"};
} else if (focus < tbounds.first) {
// over left end of timeline(s): stop rightward drag
me.dragScopeState = {state:"over-left"};
} else {
me.dragScopeState = {state:"okay"};
}
*/
if (MED.scopeChanges == 1) {
// first scope change after initial load
me.initiateNavigation();
}
MED.scopeChanges++;
});
// listen for focus date change
// mainly if date is zipped-to rather than dragged
$.subscribe(container_name + ".mediator.focusDateChange", function () {
me.displayFocusDate();
});
// CREATE TIMELINES MENU
$.subscribe(container_name + ".mediator.timelineDataLoaded", function (arg) {
if (MED.singleTimelineID) {
me.setupSingleTimeline();
} else {
me.buildTimelineMenu(MED.timelineCollection);
if (timeglider.mode == "presentation") {
me.setupPresentation();
}
}
me.buildSettingsMenu();
me.setupFilter();
$(".timeglider-loading").fadeOut(500);
});
$.subscribe(container_name + ".mediator.activeTimelinesChange", function () {
$(me._views.TIMELINE_MENU_UL + " li").each(function () {
var id = $(this).data("timeline_id");
if (_.indexOf(MED.activeTimelines, id) != -1) {
$(this).addClass("activeTimeline");
} else {
$(this).removeClass("activeTimeline");
}
}); // end each
});
$.subscribe(container_name + ".mediator.filterChange", function () {
// refresh is done inside MED -- no need to refresh here
});
/* END PUB-SUB SUBSCRIBERS */
$.subscribe(container_name + ".mediator.resize", function () {
me.resize();
});
//// GESTURES ////
/* !!TODO Still a FAIL in iPad ----
PRIVATE/SCOPED IN CLOSURE, THESE ARE UN-TESTABLE
*/
function gestureChange (e) {
e.preventDefault ();
if (MED.gesturing === false) {
MED.gesturing = true;
MED.gestureStartZoom = MED.getZoomLevel();
}
var target = e.target;
// constant spatial converter value
//$("#output").append("<br>start zoom:" + MED.gestureStartZoom);
// This basically works, but it's funky still....
var g = Math.ceil(MED.gestureStartZoom / (e.scale / 2));
//$("#output").append("<br>new gest zoom:" + g);
MED.setZoomLevel(g);
}
function gestureEnd (e) {
MED.gesturing = false;
}
if ($.support.touch) {
// alert("widget:" + WIDGET_ID);
$("#" + WIDGET_ID).addTouch();
var tgcompnt = document.getElementById(WIDGET_ID);
tgcompnt.addEventListener("gesturestart", function (e) {
e.preventDefault();
$("#output").append("<br>gesture zoom:" + MED.getZoomLevel());
}, false);
tgcompnt.addEventListener("gestureend", function (e) {
e.preventDefault();
$("#output").append("<br>gesture end:" + MED.getZoomLevel());
}, false);
tgcompnt.addEventListener("gesturechange", function (e) {
e.preventDefault();
gestureChange(e);
//var gLeft = e.touches.item(0).pageX;
//var gRight = e.touches.item(1).pageX;
// var gLeft = "l", gRight = "r";
// $("#output").append("[" + gLeft + ":" + gRight + "]");
}, false);
} // end if ($.support.touch)
}
tg.TG_TimelinePlayer.prototype = {
resize: function() {
var new_height = $(PL).height();
$(CONTAINER).height(new_height);
// measure stuff
this.dimensions = this.getWidgetDimensions();
// use measurements to resize various things
this.resizeElements();
MED.refresh();
},
getWidgetDimensions : function () {
var c = $(CONTAINER),
w = c.width(),
wc = Math.floor(w / 2) + 1,
h = c.height(),
hc = Math.floor(h/2),
t_height = this.tick_height,
lft = c.position().left,
offset = c.offset(),
f_height = (options.show_footer == true) ? $(this._views.FOOTER).outerHeight() : 0,
t_top = h - f_height - t_height,
// objects to return
ticks_ht = h-(f_height+t_height),
ticks_rule_ht =
h_height = $(this._views.HEADER).outerHeight() || 0,
// available space for timelines
tlspace = h - (f_height + h_height + t_height),
container = {"width":w, "height":h, "centerx":wc, "centery":hc, "left": lft, "offset": offset},
ticks = {"height":ticks_ht},
tick = {"top":t_top, "height":t_height},
header = {"height":h_height},
footer = {"height":f_height};
var ret = {container:container, ticks:ticks, tick:tick, header:header, footer:footer, timeline_space:tlspace};
MED.setDimensions(ret);
return ret;
},
initiateNavigation: function() {
var me = this;
$(".tg-single-timeline-header .tg-timeline-start").fadeIn();
$(CONTAINER).delegate(".tg-single-timeline-header h2", CLICKORTOUCH, function() {
var tid = $(this).data("timeline_id");
me.timelineModal(tid);
MED.focusTimeline(tid);
});
},
displayZoomLevel : function() {
var me=this,
zl = MED.getZoomLevel();
if (zl > 0) {
if (options.display_zoom_level == true) {
$(me._views.ZOOM_DISPLAY).text(zl);
}
}
},
displayFocusDate: _.throttle(function () {
// this is expensive for real-time dragging...
// without throttle, leads to crashing in Firefox
var fd = MED.getFocusDate();
var str = fd.format("d MMM yyyy", true);
$(this._views.FOCUS_DATE).find("span").text(str);
}, 300),
displayOutOfFrameEvents: _.throttle(function() {
var leftOfFrame = MED.getPastEvents(true, true);
var rightOfFrame = MED.getFutureEvents(true, true);
}, 900),
/**
* setPanButton
* @param $sel {jquery dom selector} the button to be assigned
* @parm vel {Number} positive for moving to the right, negative for moving left
*
*
*/
setPanButton : function ($sel, vel) {
var me = this,
_int = 33; // 33/1000 second interval
$($sel).bind("mousedown", function () {
me.intervalMachine("pan", {type:"set", fn: me.pan, args:[vel], intvl:_int}); })
.bind("mouseup", function () {
me.intervalMachine("pan", {type:"clear", fn: me.pan, callback: "resetTicksHandle"}); })
.bind("mouseout", function () {
me.intervalMachine("pan", {type:"clear", fn: me.pan, callback: "resetTicksHandle"}); });
},
/*
* intervalMachine
* param name {String} JS interval ref. name
* @param info {Object}
* type: clear | set
* fn: function to call on interval
* callback: function to invoke upon clearing
* eg: {type:"clear", fn: me.pan, callback: "resetTicksHandle"}
*
*
* PLUGIN CANDIDATE!
*/
intervalMachine : function (name, info) {
var me=this;
if (info.type === "clear") {
clearInterval(intervals[name]);
if (info.callback) {
me[info.callback]();
}
} else {
// run it
intervals[name] = setInterval(function () {
info.fn.apply(me, info.args);
}, info.intvl);
}
},
invSliderVal : function(v) {
return Math.abs(v - 101);
},
/*
* pan
* @param dir {Number}
* simply moves the ticks one way or another
* To work properly, it needs a resetTicksHandle() callback;
* Using this with intervalMachine()
*/
pan: function (dir) {
var d = dir || 20,
$t = $(TICKS),
newPos = $t.position().left + d;
$t.css({left:newPos});
MED.setTicksOffset(newPos);
},
registerTitles: function () {
var toff, w, tw, sw, pos, titx,
$elem, $env, env, $tb, $ti, relPos, tbWidth,
mo = $(CONTAINER).offset().left,
trackTB = true;
var tks = $(this._views.TICKS).position().left;
$(".tg-lane").css("left", (-1 * tks));
$(CONTAINER + " .timeglider-event-spanning").each(
function() {
// !TODO needs optimizing of DOM "touching"
var $spev = $(this);
toff = $spev.offset().left - mo;
$elem = $spev.find(".timeglider-event-title");
$laneTitle = $spev.find(".timeglider-event-spanner span");
tw = $elem.outerWidth() || $laneTitle.outerWidth();
sw = $spev.find(".timeglider-event-spanner").outerWidth();
// if the span is wider than the title element
if (sw > tw && tw) {
// if the offset is to the left of the frame
if (toff < 0) {
var dif = sw-tw;
if (Math.abs(toff) < dif) {
$elem.css({marginLeft:(-1 * toff) + 5});
$laneTitle.css({marginLeft:(-1 * toff) + 5});
} else {
// keep it aligned right if the right side is poking in
$elem.css({marginLeft:(sw - tw) - 5});
$laneTitle.css({marginLeft:(sw - tw) - 5});
}
// otherwise just keep it aligned on the left side of the span
} else {
$elem.css({marginLeft:5});
}
}
// is offscreen == false: $(this).removeClass('timeglider-event-offscreen')
}
);
// IE 7,8 not able to find the .titleBar element below
// while this .each is happening. Performance in .find()?
// This hack just turns off the titleBar tracking... :(
/*
if ($.browser.msie && parseInt($.browser.version) <9) {
trackTB = false;
}
*/
// if (trackTB === true) {
$(CONTAINER + " .tg-timeline-envelope").each(
function () {
// !TODO needs optimizing of DOM "touching"
$env = $(this);
env = $env.offset().left - mo;
$tb = $env.find(".titleBar");
// `pos` is a pre-cached $tb.position().left;
// rather than calculating position here, it's
// grabbing a cached value stored in element data()
pos = $tb.data("lef");
relPos = -1 * (pos + env);
$ti = $tb.find(".timeline-title");
// if it's pushed left of the window
if ( (relPos > 0) ) {
var dif = $tb.width()-$ti.width();
if (relPos < dif) {
$ti.css({marginLeft:relPos + 5});
} else {
$ti.css({marginLeft:dif - 5});
}
} else {
$ti.css({marginLeft:5});
}
}
);
}, // end register titles
registerDragging : function () {
/*
startSec --> the seconds-value of the
initial focus date on landing @ zoom level
*/
//!TODO: See if we can throttle this to be only
// once every 100ms....
var startSec = MED.startSec,
tickPos = $(TICKS).position().left,
secPerPx = MED.getZoomInfo().spp;
var newSec = startSec - (tickPos * secPerPx);
var newD = new TG_Date(newSec);
MED.setFocusDate(newD);
// remove this???
this.displayFocusDate();
this.displayOutOfFrameEvents();
},
getTimelinesTagsArray: function() {
var me=this, tl, tags = [], tags_obj;
_.each(MED.timelineCollection.models, function(tl) {
tags_obj = tl.get("tags");
_.each(tags_obj, function(val,key) {
tags.push(key);
});
});
return tags;
},
/* FILTER BOX SETUP */
setupFilter : function () {
var me = this,
$bt = $(me._views.FILTER_BT),
$filter = $.tmpl(me._templates.filter_modal,{}).appendTo(me._views.CONTAINER),
use_title = false,
use_desc = false,
fbox = me._views.FILTER_BOX;
var clearFilterFront = function() {
MED.setFilters({origin:"title_andor_desc", title:'', tags:'', description:''});
$(fbox + " .timeglider-filter-search").val('');
$(fbox + " .timeglider-filter-tags").val('');
$("#filter-tags").val("").trigger("change");
}
var clearFilters = function() {
MED.clearFilters({"legend":false, "custom":false});
}
// get tags array from active timelines
var activeTags = me.getTimelinesTagsArray();
if (activeTags.length > 0) {
if ($.fn.select2) {
$("#filter-tags").select2({
tags:activeTags,
placeholder:"Click to select",
allowClear:true
});
}
} else {
// no tags -- hide tags element
$(".filter-tags").hide();
}
$filter.position({
my: "right+32 bottom-16",
at: "right top",
of: $(me._views.FILTER_BT)
}).css("z-index", me.ztop++).hide();
$(CONTAINER)
.delegate(".timeglider-filter-box .tg-close-button", "click", function () {
clearFilterFront();
$filter.fadeOut();
})
$(me._views.FILTER_BT).bind("click", function() {
$filter.fadeIn();
var $bt = $(this);
// If it's never been opened, apply actions to the buttons, etc
if (me.filterBoxActivated == false) {
me.filterBoxActivated = true;
var $filter_apply = $(fbox + " .timeglider-filter-apply"),
$filter_clear = $(fbox + " .timeglider-filter-clear"),
incl = "", tags = "", excl = "", title_txt = "", desc_txt = "";
var $filter_input = $(fbox + " .timeglider-filter-search");
$filter_input.on("keydown", function(e) {
switch(e.which) {
case 0: case 9: case 13:
$filter_apply.trigger("click");
break;
}
});
// set up listeners
$filter_apply.bind("click", function () {
clearFilters();
tags = $("#filter-tags").val();
incl = $(fbox + " .timeglider-filter-search").val();
excl = ""; // $(fbox + " .timeglider-filter-exclude").val();
use_title = $(fbox + " input#filter_t").is(":checked");
use_desc = $(fbox + " input#filter_d").is(":checked");
if ((use_title && incl) || (use_desc && incl) || tags) {
title_txt = use_title ? incl: "";
desc_txt = use_desc ? incl: "";
if(use_title && use_desc && incl) {
// EITHER title OR description match
MED.setFilters({origin:"clude", include:title_txt, exclude:"", tags:tags});
} else {
// just title, or just description match
MED.setFilters({origin:"title_andor_desc", title:title_txt, description:desc_txt, tags:tags});
}
} else {
// clear
clearFilters();
clearFilterFront();
}
});
$filter_clear.bind("click", function () {
clearFilters();
clearFilterFront();
});
} // end if filterBoxActivated
}); // end FILTER_BT click
}, // end setupFilter
buildTimelineMenu : function () {
var me=this;
var $menu;
var $bts = $(me._views.FOOTER).find(".tg-footer-buttons");
var $menu_bt = {};
if (! $(me._views.TIMELINE_LIST_BT)[0]) {
$menu_bt = $("<div class='timeglider-footer-button timeglider-list-bt'></div>").appendTo($bts);
} else {
$menu_bt = $(me._views.TIMELINE_LIST_BT);
}
if ($(me._views.TIMELINE_MENU)[0]) {
$(me._views.TIMELINE_MENU).remove()
}
var $menu = $.tmpl(me._templates.timeline_list_modal,{}).appendTo(me._views.CONTAINER);
// each timeline's <li> item in menu
var menuItem = Backbone.View.extend({
initialize: function (){
this.model.bind('change', this.render, this);
},
tagName: "li",
template: "${title}",
events: {
"click": "toggleTimeline"
},
toggleTimeline : function() {
MED.toggleTimeline(this.model.get("id"));
},
render: function() {
var tid = this.model.get("id"), activeClass = "";
if ( MED.isActive(tid)) activeClass = "activeTimeline";
$(this.el).html($.tmpl(this.template, this.model.attributes)).data("timeline_id", tid).addClass(activeClass);
return this;
}
});
$(me._views.TIMELINE_MENU_UL).html("");
_.each(MED.timelineCollection.models, function(model){
$(me._views.TIMELINE_MENU_UL).append(new menuItem({model:model}).render().el);
});
$menu.position({
my: "right+50 bottom-12",
at: "left top",
of: $menu_bt
}).hide();
$menu.find(".tg-close-button").bind(CLICKORTOUCH, function () {
$menu.fadeOut();
});
$menu_bt.bind(CLICKORTOUCH, function() {
$menu.fadeIn();
})
},
getTimezonePulldown: function(id, sel){
var html = "<select name='timezone' id='" + id + "'>",
seld = false, selstr = "selected";
$.map(TG_Date.timezones, function(tz){
if (sel == tz.offset && seld == false) {
selstr = "selected";
seld = true;
} else {
selstr = "";
}
html += "<option value='" + tz.offset + "' " + selstr + ">" + tz.name + "</option>";
});
html += "</select>";
return html;
},
buildSettingsMenu: function () {
var me = this;
var $s = $.tmpl(me._templates.settings_modal,{}).appendTo(me._views.CONTAINER);
var tz_menu = this.getTimezonePulldown("timeglider-settings-timezone", MED.timeOffset.string);
$s.find(".timeglider-settings-timezone")
.append('<span class="settings-label">timezone:</span> ' + tz_menu + '<br><a class="btn" id="timeglider-settings-save">save</a>');
$s.position({
my: "right+32 bottom-16",
at: "right top",
of: $(me._views.SETTINGS_BT)
}).hide();
$(CONTAINER)
.delegate(".timeglider-settings-modal .tg-close-button", "click", function () {
$s.fadeOut();
});
$(me._views.SETTINGS_BT).bind(CLICKORTOUCH, function() {
$s.fadeIn();
});
$s.find("#timeglider-settings-save").bind(CLICKORTOUCH, function() {
// get timezone
var tz_off = $(CONTAINER + " #timeglider-settings-timezone").val();
MED.setTimeoffset(tz_off);
$(".timeglider-settings-modal").fadeOut();
});
},
setupSingleTimeline: function() {
var me = this, dims = {},
tid = MED.singleTimelineID,
timeline = MED.timelineCollection.get(tid);
if (MED.options.display_single_timeline_info != false) {
var title = "<h2 data-timeline_id='" + tid + "'>" + timeline.get("title") + "</h2>";
inf = (timeline.get("description")) ? "<li id='info' class='timeline-info-bt' data-timeline_id='" + tid + "'>info</li>":"",
leg = (timeline.get("hasLegend")) ? "<li id='legend' class='tg-legend-bt' data-timeline_id='" + tid + "'>legend</li>":"",
tools = ""; // "<a id='tools' class='tools-bt noselect'>tools</a>",
tmpl = "<div class='tg-widget-header tg-single-timeline-header'><ul><li class='tg-timeline-start' data-timeline_id='" + tid + "'>start</li></ul></div>";
$st = $(tmpl).appendTo(CONTAINER);
me.singleTitleHeight = $st.outerHeight();
} else {
me.singleTitleHeight = 0;
}
me.dimensions = dims = me.getWidgetDimensions();
if (timeline.get("hasImageLane")) {
me.buildImageLane();
}
// adjusts the zoom slider away from the timeline bar at top
if (options.inverted == true) {
//
$(me._views.SLIDER_CONTAINER).css({"bottom":"16px","top":"auto"});
$(me._views.FOOTER).css({"top":dims.header.height + "px"});
timeline.set({"bottom":(dims.timeline_space - 70), "inverted":true});
options.tick_top = dims.container.height - (dims.timeline_space + me.tick_height);
} else {
$(me._views.SLIDER_CONTAINER).css("top", me.singleTitleHeight + 4);
}
if (options.initial_timeline_modal != false) {
me.timelineModal(tid);
}
if (timeline.get("hasLegend")) {
setTimeout(function() {
me.legendModal(tid);
}, 500);
}
},
//////// MODALS
presentationModal : function () {
var me = this;
var pmodal = $(CONTAINER).find("#presInfoModal");
if (!pmodal[0]) {
if (MED.presentation.description) {
var ch = me.dimensions.container.height,
modal = new this.presInfoModal({model:MED.presentation});
var $header = $(CONTAINER).find(".tg-pres-header");
$(modal.render().el)
.appendTo($(".timeglider-container"))
.position({
my: "left top",
at: "left+12 bottom+12",
of: $header,
collision: "flip fit"
})
.css({"z-index":me.ztop++, "max-height":ch-64});
if ($.jScrollPane) {
$(".jscroll").jScrollPane();
}
}
}
},
//////// MODALS
datePickerOpen: false,
datepickerModal : function () {
var me = this;
var $modal = {};
if (!me.datepickerOpen) {
var me = this,
ch = me.dimensions.container.height,
modal = new this.datepicker({model:{}}),
$modal = $(modal.render().el)
.appendTo($(".timeglider-container"))
.position({
my: "center bottom-39",
at: "center bottom",
of: $(".timeglider-container"),
collision: "fit fit"
})
.css({"z-index":me.ztop++, "max-height":ch-64});
me.datepickerOpen = true;
} else {
}
},
startPresentation: function() {
var me = this,
pres = MED.presentation;
MED.gotoDateZoom(pres.focus_date.dateStr, pres.initial_zoom);
},
setupPresentation: function() {
var me = this,
pres = MED.presentation;
var title = "<h2 id='pres_title' class='no-select' data-timeline_id='primary'>" + pres.title + "</h2>",
inf = (pres.description) ? "<li id='info' class='pres-info-bt'>info</li>":"",
leg = (pres.legend) ? "<li id='legend' data-timeline_id='pres' class='tg-legend-bt'>legend</li>":"",
tools = "", // "<a id='tools' class='tools-bt noselect'>tools</a>",
tmpl = "<div class='tg-widget-header tg-pres-header'>" + title + "<ul>" + inf + leg + "<li class='tg-pres-start'>start</li></ul>" + tools + "</div>",
$st = $(tmpl).appendTo(CONTAINER);
// end vars
me.getWidgetDimensions();
me.singleTitleHeight = $st.outerHeight();
if (pres.image_lane_height) {
me.buildImageLane();
} // end if has imagelane
// adjusts the zoom slider away from the timeline bar at top
$(me._views.SLIDER_CONTAINER).css("top", me.singleTitleHeight + 4);
me.startPresentation();
if (pres.open_modal && pres.description) {
me.presentationModal();
}
},
buildImageLane: function() {
var me = this;
var $existing = $(CONTAINER).find(".tg-image-lane-pull");
if ($existing.length == 0) {
var $imageLane = $("<div class='tg-image-lane-pull'><div title='This is the image lane!' class='tg-image-lane-bg'></div></div>").appendTo(CONTAINER);
$imageLane.draggable({
axis:"y",
containment: "parent",
drag: function () {
var $pull = $(this);
var ypos = $pull.position().top;
if (ypos > 400) {
$pull.css("top", 400);
return false;
} else if (ypos < 5) {
$pull.css("top", 5);
return false;
}
},
stop:function() {
MED.setImageLaneHeight($(this).position().top - me.singleTitleHeight, true, false);
}
});
me.setImageLaneHandle();
}
},
/*
* setImageLaneHandle
* gets image_lane_height from MED and sets image lane
* UI remotely (not from dragging, but from timeline/pres props)
*/
setImageLaneHandle: function () {
var me = this;
var newHt = parseInt(MED.image_lane_height, 10) + parseInt(me.singleTitleHeight, 10);
$(".tg-image-lane-pull").css("top", newHt + "px");
},
/*
Zoom slider is inverted value-wise from the normal jQuery UI slider
so we need to feed in and take out inverse values with invSliderVal()
*/
buildSlider : function () {
var me = this,
iz = MED.getZoomLevel();
if (options.min_zoom == options.max_zoom) {
// With a single zoom level, hide the zoom controller
$(this._views.SLIDER_CONTAINER).css("display", "none");
} else {
if (options.display_zoom_level == true) {
var $zl = $("<div>").appendTo(this._views.SLIDER_CONTAINER).addClass("timeglider-zoomlevel-display");
$zl.html('&nbsp;');
}
var me = this,
init_zoom = me.invSliderVal(iz),
hZoom = MED.max_zoom,
lZoom = MED.min_zoom,
sHeight = (1 + hZoom - lZoom) * 3;
$(this._views.SLIDER)
.css({"height":sHeight})
.slider({
steps: 100,
handle: $('.knob'),
animate:300,
orientation: 'vertical',
// "min" here is really the _highest_ zoom value @ upside down
min:me.invSliderVal(hZoom),
// "max" actually takes (i nverse value of) low zoom level
max:me.invSliderVal(lZoom),
value:init_zoom,
start: function (e, ui) {
// show zoom level legend
me.sliderActive = true;
},
stop: function (e, ui) {
// hide zoom level legend
me.sliderActive = false;
},
change: function(e,ui){
// i.e. on-release handler
// possibly load throttled back events
},
slide: function(e, ui) {
// sets model zoom level to INVERSE of slider value
MED.setZoomLevel(me.invSliderVal(ui.value));
}
}); // end .slider()
} // end--if min_zoom == max_zoom
},
setupScroller: function() {
if (options.event_overflow == "scroll") {
$(".timeglider-slider-container").css("right", "22px");
$(CONTAINER + " .tg-scroller-handle").draggable({
containment: "parent",
drag: function() {
// move content vertically
}
});
}
},
adjustScroller: function(low, high) {
var me = this;
if (options.event_overflow == "scroll") {
var $scr = $(CONTAINER + " .tg-scroller");
var $handle = $(CONTAINER + " .tg-scroller-handle");
var scr_ht = me.dimensions.scroller_height;
var evts_ht = high + (Math.abs(low));
var overage = evts_ht / scr_ht;
if (overage > 1) {
} else {
$scr.hide();
}
}
},
/*
* usage: timeline event hovering, modal display
*
*/
getEventDateLine: function(ev) {
var startDateF = "<span class='timeglider-dateline-startdate'>" + ev.startdateObj.format('', true, MED.timeOffset) + "</span>",
endDateF = "";
if (ev.span == true) {
endDateF = " &ndash; <span class='timeglider-dateline-enddate'>" + ev.enddateObj.format('', true, MED.timeOffset) + "</span>";
}
var ret = startDateF + endDateF;
return ret;
},
eventHover : function ($ev, ev) {
var follow = true;
if (typeof MED.options.eventHover == "function") {
follow = MED.options.eventHover($ev, ev, this);
}
if (follow) {
var me = this,
$hov = $(".timeglider-event-hover-info"),
title = "",
date_line = "";
$ev.append("<div class='tg-event-hoverline'></div>").addClass("tg-event-hovered");
if (ev.date_display == "no") {
date_line = "";
} else {
date_line = me.getEventDateLine(ev);
}
if ($ev.hasClass("tg-event-collapsed") || $ev.hasClass("tg-event-overflow")) {
title = "<div>" + ev.title + "</div>";
} else {
title = "";
}
if (title || date_line) {
$hov.position({
my: "left+1 top+4",
at: "left bottom",
of: $ev,
collision: "flip flip"}
)
.html(title + date_line)
}
}
},
eventUnHover : function ($ev) {
var $ev = $ev || "";
if (typeof MED.options.eventUnHover == "function") {
MED.options.eventUnHover($ev, this);
} else {
$(".timeglider-event-hover-info").css("left", "-1000px");
$(".timeglider-timeline-event").removeClass("tg-event-hovered");
if ($ev) {
$ev.find(".tg-event-hoverline").remove();
}
}
},
clearTicks : function () {
this.leftside = 0;
this.tickNum = 0;
$(TICKS)
.css("left", 0);
// .html("<div class='timeglider-handle'></div>");
// remove everything but HANDLE, which
// needs to stay so that gesturing (pinching to zoom)
// doesn't lose its target
$(CONTAINER + " .tg-timeline-envelope").remove();
$(CONTAINER + " .timeglider-tick").remove();
},
/*
The initial drawing of a full set of ticks, starting in the
middle with a single, date-focused div with type:"init", after which
a left-right alternating loop fills out the width of the current frame
*/
castTicks: function (orig) {
if (MED.viewMode == "timeline") {
this.clearTicks();
var zLevel = MED.getZoomLevel(),
fDate = MED.getFocusDate(),
zInfo = MED.getZoomInfo(),
tickWidth = zInfo.width,
twTotal = 0,
ctr = this.dimensions.container.centerx,
// determine how many are necessary to fill (overfill) container
nTicks = Math.ceil(this.dimensions.container.width / tickWidth) + 4,
leftright = 'l';
if (typeof zInfo.width == "number") {
MED.setTicksReady(false);
// INITIAL TICK added in center according to focus date provided
this.addTick({"type":"init", "focus_date":fDate});
// ALTERNATING L & R ticks
for (var i=1; i<=nTicks; i +=1) {
this.addTick({"type":leftright});
// switch l and r for alternating layout action
leftright = (leftright == "l") ? "r" : "l";
}
MED.setTicksReady(true);
this.displayFocusDate();
}
} else {
// i.e. viewMode == list
this.clearTicks();
this.freshTimelines();
}
},
getTickTop: function () {
var tttype = typeof MED.options.tick_top;
if (tttype == "number") {
// default number, zero for ticks at top
return MED.options.tick_top;
} else if (tttype == "function") {
// could be a custom setter function
return MED.options.tick_top(me.dimensions);
} else {
// at the bottom
return parseInt(this.dimensions.tick.top);
}
},
/*
* @param info {object} --object-->
* type: init|l|r
* focusDate: date object for init type
*/
addTick: function (info) {
var me = this, mDays = 0, dist = 0, pos = 0,
tperu = 0, serial = 0, shiftLeft = 0, ctr = 0,
tid = "", tickHtml = "", sub_label = "", label = {},
$tickDiv = {}, tInfo = {}, pack = {}, mInfo = {},
sub_labels = "", sub_labels_arr = [], oeClass = '',
tickUnit = MED.getZoomInfo().unit,
tickWidth = MED.getZoomInfo().width,
focusDate = MED.getFocusDate(),
tick_top = me.getTickTop(),
serial = MED.addToTicksArray({type:info.type, unit:tickUnit}, focusDate);
// end vars comma list
// adjust tick-width for months (mo)
if (tickUnit == "mo") {
// starts with default tickWidth set for 28 days: How many px, days to add?
mInfo = TG_Date.getMonthAdj(serial, tickWidth);
tickWidth = mInfo.width;
mDays = mInfo.days;
}
// tickNum has been reset to zero by refresh/zoom
this.tickNum ++;
if (info.type == "init") {
shiftLeft = this.tickOffsetFromDate(MED.getZoomInfo(), MED.getFocusDate(), tickWidth);
pos = Math.ceil(this.dimensions.container.centerx + shiftLeft);
$(TICKS).data("init-left", pos);
// both and left and right sides are defined
// here because it is the first tick on screen
this.leftside = pos;
this.rightside = (pos + tickWidth);
} else if (info.type == "l") {
pos = Math.floor(this.leftside - tickWidth);
this.leftside = pos;
} else if (info.type == "r") {
pos = Math.floor(this.rightside);
this.rightside += tickWidth;
}
// turn this into a function...
MED.getTickBySerial(serial).width = tickWidth;
MED.getTickBySerial(serial).left = pos;
oeClass = (serial % 2 == 0) ? "tg-even-tick": "tg-odd-tick";
tid = this._views.PLACE + "_" + tickUnit + "_" + serial + "-" + this.tickNum;
$tickDiv= $("<div class='timeglider-tick " + oeClass + "' id='" + tid + "'><div class='tg-tick-body'><div class='tg-tick-leftline'></div><div class='timeglider-tick-label'></div><div class='tg-tick-label-bottom'></div></div>")
.appendTo(TICKS);
$tickDiv.css({width:tickWidth, left:pos, top:tick_top, zIndex:55});
// GET TICK DIVS FOR unit AND width
tInfo = this.getTickMarksInfo({unit:tickUnit, width:tickWidth});
// if there's a value for month-days, us it, or use
// tperu = (mDays > 0) ? mDays : tInfo.tperu;
tperu = mDays || tInfo.tperu;
dist = tickWidth / tperu;
// Add tick-lines or times when divisions are spaced wider than 5
if (dist > 8) {
// As of Jan 29, 2012, no more Raphael!
var c, l, xd, stk = '', sl4hd = 0,
ht = 10, downset = 20, hr_info = {}, ampm = '',
lpos = 0;
for (l = 0; l < tperu; l++) {
sub_label = "&nbsp;";
if (dist > 16) {
if (tickUnit == "da") {
// hours starting with 0
sub_label = me.getHourLabelFromHour(l, dist);
} else if (tickUnit == "mo") {
// days starting with 1
sub_label = l + 1;
} else if (tickUnit == "ye") {
if (dist > 30){
// Jan, Feb, Mar...
sub_label = "&nbsp;" + TG_Date.monthNamesAbbr[l+1];
} else {
// month abbrevs: J, F, M...
sub_label = "&nbsp;" + TG_Date.monthNamesLet[l+1];
}
} else if (tickUnit == "de") {
if (dist > 54){
sub_label = (serial *10) + l;
}
} else if (tickUnit == "ce") {
if (dist > 38){
sub_label = ((serial *10) + l) * 10;
}
}
} else {
// less than 16
sub_label = "";
}
sub_labels_arr.push("<div class='timeglider-tick-sub-label " + tickUnit + "' style='left:" + lpos + "px;width:" + dist + "px'>" + sub_label + "</div>");
lpos += dist;
}
if (serial < 0) {
sub_labels_arr.reverse();
}
sub_labels = sub_labels_arr.join("");
} else {
sub_labels = "";
}// end dist > 5 if there's enough space between tickmarks
// add hours gathered in loop above
if (sub_labels) {
$tickDiv.append("<div class='tg-tick-sublabel-group' style='width:" + (tickWidth + 10) + "px;'>" + sub_labels + "</div>");
}
pack = {"unit":tickUnit, "width":tickWidth, "serial":serial};
label = this.getDateLabelForTick(pack);
// In order to gather whether an outlier span is
// occuring on drag-right (the right side of a span)
// we need some seconds...
pack.seconds = this.getTickSeconds[tickUnit](pack.serial);
// DO OTHER STUFF TO THE TICK, MAKE THE LABEL AN ACTIONABLE ELEMENT
// SHOULD APPEND WHOLE LABEL + TICKLINES HERE
$tickDiv.find(".timeglider-tick-label").text(label);
var lb_offset = (MED.options.show_footer) ? 46 : 14;
var ht = this.dimensions.container.height - lb_offset;
$tickDiv.find(".tg-tick-label-bottom").text(label).css("top", ht);
return pack;
/* end addTick */
},
resizeElements: function () {
var me = this,
// measurements have just been taken...
cx = me.dimensions.container.centerx,
ch = me.dimensions.container.height,
cw = me.dimensions.container.width,
fh = me.dimensions.footer.height,
th = me.dimensions.tick.height,
$C = $(this._views.CENTERLINE),
$D = $(this._views.DATE),
dleft = cx - ($D.width() / 2);
if (MED.options.show_centerline === true) {
$C.css({"height":ch, "left": cx});
} else {
$C.css({"display":"none"});
}
var ticks_ht = ch-(fh+th);
$(this._views.TICKS).css("height", ticks_ht);
var scroller_ht = ticks_ht - 4;
if (options.event_overflow == "scroll") {
me.dimensions.scroller_height = scroller_ht;
$(this._views.CONTAINER).find(".tg-scroller").css({
height:scroller_ht + "px"
});
} else {
me.dimensions.scroller_height = 0;
$(this._views.CONTAINER).find(".tg-scroller").remove();
}
if (ch < 500 || cw < 500 ) {
$(".timeglider-slider").css("display", "none");
} else {
$(".timeglider-slider").css("display", "block");
}
$D.css({"left":dleft});
},
/*
* @param pack {Object} `unit` and `serial`
*/
getTickSeconds: {
da: function(ser) {
var s = ser * 86400,
e = s + 86400;
return {start:s, end:e};
},
mo: function(ser) {
var s = ser * 2629800,
e = s + 2629800;
return {start:s, end:e};
},
ye: function(ser) {
var s = ser * 31540000,
e = s + 31540000;
return {start:s, end:e};
},
de: function(ser) {
var s = ser * 315400000,
e = s + 315400000;
return {start:s, end:e};
},
ce: function(ser) {
var s = ser * 3154000000,
e = s + 3154000000;
return {start:s, end:e};
},
thou: function(ser) {
var s = ser * 3154000000,
e = s + 3154000000;
return {start:s, end:e};
},
tenthou: function(ser) {
var s = ser * 3154000000,
e = s + 3154000000;
return {start:s, end:e};
},
hundredthou: function(ser) {
var s = ser * 3154000000,
e = s + 3154000000;
return {start:s, end:e};
},
mill: function(ser) {
var s = ser * 3154000000,
e = s + 3154000000;
return {start:s, end:e};
},
tenmill: function(ser) {
var s = ser * 3154000000,
e = s + 3154000000;
return {start:s, end:e};
},
hundredmill: function(ser) {
var s = ser * 31540000000,
e = s + 31540000000;
return {start:s, end:e};
},
bill: function(ser) {
var s = ser * 315400000000,
e = s + 315400000000;
return {start:s, end:e};
}
},
getHourLabelFromHour : function (h24, width) {
var ampm = "", htxt = h24, bagels = "", sublabel = "", sl4hd = 0;
if (width < 16) {
// no room for anything; will just be ticks
return '';
} else {
if (h24 > 12) {
htxt = h24-12;
} else if (h24 == 0) {
htxt = 12;
}
if (width > 30) {
ampm = (h24 > 11) ? " pm" : " am";
}
if (width > 200) {
sl4hd = width/4 - 4;
return "<div class='minutes' style='width:" + sl4hd + "px'>" + htxt + ":00 " + ampm + "</div>"
+ "<div class='minutes' style='width:" + sl4hd + "px'>" + htxt + ":15 " + ampm + "</div>"
+ "<div class='minutes' style='width:" + sl4hd + "px'>" + htxt + ":30 " + ampm + "</div>"
+ "<div class='minutes' style='width:" + sl4hd + "px'>" + htxt + ":45 " + ampm + "</div>";
} else {
bagels = (width > 60) ? ":00" : "";
return htxt + bagels + ampm;
}
}
},
/* provides addTick() info for marks and for adj width for month or year */
getTickMarksInfo : function (obj) {
var tperu;
switch (obj.unit) {
case "da":
tperu = 24;
break;
case "mo":
// this is adjusted for different months later
tperu = 30;
break;
case "ye":
tperu = 12;
break;
default: tperu = 10;
}
return {"tperu":tperu};
},
/*
* getDateLabelForTick
* determines label for date unit in "ruler"
* @param obj {object} carries these values:
{"unit":tickUnit, "width":tickWidth, "serial":serial}
*
*/
getDateLabelForTick : function (obj) {
var i, me=this, ser = obj.serial, tw = obj.width;
switch(obj.unit) {
case "bill":
if (ser == 0) {
return "1";
} else if (ser > 0) {
return (ser) + " billion";
} else {
return (ser) + " b.y. bce";
}
break;
case "hundredmill":
if (ser == 0) {
return "1";
} else if (ser > 0) {
return (ser) + "00 million";
} else {
return (ser) + "00 m.y. bce";
}
break;
case "tenmill":
if (ser == 0) {
return "1";
} else if (ser > 0) {
return (ser) + "0 million";
} else {
return (ser) + "0 m.y. bce";
}
break;
case "mill":
if (ser == 0) {
return "1";
} else if (ser > 0) {
return (ser) + " million";
} else {
return (ser) + " m.y. bce";
}
break;
case "hundredthou":
if (ser == 0) {
return "1";
} else if (ser > 0) {
return (ser) + "00,000";
} else {
return (ser) + "00,000 bce";
}
break;
case "tenthou":
if (ser == 0) {
return "1";
} else if (ser > 0) {
return (ser) + "0,000";
} else {
return (ser) + "0,000 bce";
}
break;
case "thou":
if (ser == 0) {
return "1" + "(" + ser + ")";
} else if (ser > 0) {
return (ser) + "000";
} else {
return (ser) + "000 bce";
}
break;
case "ce":
if (ser == 0) {
return "1" + "(" + ser + ")";
} else if (ser > 0) {
return (ser) + "00";
} else {
return (ser) + "00 bce";
}
break;
case "de":
if (ser > 120){
return (ser * 10) + "s";
} else {
return (ser * 10);
}
break;
case "ye":
return ser;
break;
case "mo":
i = TG_Date.getDateFromMonthNum(ser);
if (tw < 120) {
return TG_Date.monthNamesAbbr[i.mo] + " " + i.ye;
} else {
return TG_Date.monthNames[i.mo] + ", " + i.ye;
}
break;
case "da":
// COSTLY: test performance here on dragging
i = new TG_Date(TG_Date.getDateFromRD(ser));
if (tw < 120) {
return i.ye + "-" + TG_Date.monthNamesAbbr[i.mo] + "-" + i.da ;
} else {
return i.ye + "-" + TG_Date.monthNames[i.mo] + "-" + i.da;
}
break;
default: return obj.unit + ":" + ser + ":" + tw;
}
},
/*
* tickHangies
* When dragging the interface, we detect when to add a new
* tick on left or right side: whether the outer tick has
* come within a 100px margin of the left or right of the frame
*
*/
tickHangies : function () {
var tPos = $(TICKS).position().left,
lHangie = this.leftside + tPos,
rHangie = this.rightside + tPos - this.dimensions.container.width,
tickPack, added = false,
me = this;
if (lHangie > -100) {
tickPack = this.addTick({"type":"l"});
me.appendTimelines(tickPack, "left");
} else if (rHangie < 100) {
tickPack = this.addTick({"type":"r"});
me.appendTimelines(tickPack, "right");
}
},
/* tickUnit, fd */
tickOffsetFromDate : function (zoominfo, fdate, tickwidth) {
// switch unit, calculate width gain or loss.... or just loss!
var w = tickwidth,
u = zoominfo.unit, p, prop;
switch (u) {
case "da":
// @4:30 4/24 30 / 1440
// .1666 .0201
prop = ((fdate.ho) / 24) + ((fdate.mi) / 1440);
p = w * prop;
break;
case "mo":
var mdn = TG_Date.getMonthDays(fdate.mo, fdate.ye);
prop = ((fdate.da -1) / mdn) + (fdate.ho / (24 * mdn)) + (fdate.mi / (1440 * mdn));
p = w * prop;
break;
case "ye":
prop = (((fdate.mo - 1) * 30) + fdate.da) / 365;
p = w * prop;
break;
case "de":
//
// 1995
prop = ((fdate.ye % 10) / 10) + (fdate.mo / 120);
p = w * prop;
break;
case "ce":
prop = ((fdate.ye % 100) / 100) + (fdate.mo / 1200);
p = w * prop;
break;
case "thou":
prop = ((fdate.ye % 1000) / 1000);
p = w * prop;
break;
case "tenthou":
prop = ((fdate.ye % 10000) / 10000);
p = w * prop;
break;
case "hundredthou":
prop = ((fdate.ye % 100000) / 100000);
p = w * prop;
break;
case "mill":
prop = ((fdate.ye % 1000000) / 1000000);
p = w * prop;
break;
case "tenmill":
prop = ((fdate.ye % 10000000) / 10000000);
p = w * prop;
break;
case "hundredmill":
prop = ((fdate.ye % 100000000) / 100000000);
p = w * prop;
break;
case "bill":
prop = ((fdate.ye % 1000000000) / 1000000000);
p = w * prop;
break;
default: p=0;
}
return -1 * p;
},
resetTicksHandle : function () {
var me = this;
$(me._views.HANDLE).offset({"left":$(CONTAINER).offset().left});
},
throttledResetTicksHandle: _.throttle(function () {
this.resetTicksHandle();
}, 500),
easeOutTicks : function() {
if (Math.abs(ticksSpeed) > 5) {
// This works, but isn't great:offset fails to register
// for new tim as it ends animation...
$(TICKS).animate({left: '+=' + (3 * ticksSpeed)}, 1000, "easeOutQuad", function() {
debug.trace("stopping easing", "note")
});
}
},
/*
@param obj with { tick | timeline }
@return array of event ids
This is per-timeline...
*/
getTimelineEventsByTick : function (obj) {
var unit = obj.tick.unit,
serial = obj.tick.serial,
hash = MED.eventCollection.getTimelineHash(obj.timeline.timeline_id);
if (hash[unit][serial] && hash[unit][serial].length > 0) {
// looking for an array of events...
return hash[unit][serial];
} else {
return [];
}
},
passesFilters : function (ev, zoomLevel) {
var ret = true,
ev_icon = "",
ei = "", ea = [], e, titl, desc,
ii = "", ia = [], da = [], i;
// MASTER FILTER BY THRESHOLD
if ((zoomLevel < ev.low_threshold) || (zoomLevel > ev.high_threshold)) {
return false;
}
if (MED.filters.imp_min && MED.filters.imp_min > 1) {
if (ev.importance < MED.filters.imp_min) { return false; }
}
if (MED.filters.imp_max && MED.filters.imp_max < 100) {
if (ev.importance > MED.filters.imp_max) { return false; }
}
if (MED.filters.custom && typeof MED.filters.custom == "function") {
ret = MED.filters.custom(ev, zoomLevel);
} else {
if (MED.filters.include) {
// title OR description
ret = false;
var incl = MED.filters.include;
ea = incl.split(",");
for (e=0; e<ea.length; e++) {
ei = new RegExp($.trim(ea[e]), "i");
if (ev.title.match(ei) || ev.description.match(ei)) { ret = true; }
}
} else {
// KEYWORDS FOR SHOWING THIS EVENT
if (MED.filters.title) {
titl = MED.filters.title;
ia = titl.split(",");
ret = false;
// cycle through comma separated include keywords
for (i=0; i<ia.length; i++) {
ii = new RegExp($.trim(ia[i]), "i");
if (ev.title.match(ii)) { ret = true; }
}
}
// KEYWORDS FOR SHOWING THIS EVENT
if (MED.filters.description) {
desr = MED.filters.description;
da = desr.split(",");
ret = false;
// cycle through comma separated include keywords
for (i=0; i<da.length; i++) {
ii = new RegExp($.trim(da[i]), "i");
if (ev.description.match(ii)) { ret = true; }
}
}
}
if (MED.filters.exclude) {
var excl = MED.filters.exclude;
ea = excl.split(",");
for (e=0; e<ea.length; e++) {
ei = new RegExp($.trim(ea[e]), "i");
if (ev.title.match(ei) || ev.description.match(ei)) { ret = false; }
}
}
// TAGS FILTER
if (MED.filters.tags.length > 0) {
if (ev.tags) {
// we'll assume it's not going to match first
ret = false;
ev_tags = ev.tags.split(",");
_.each(ev_tags, function(tag) {
tag = $.trim(tag);
if (_.indexOf(MED.filters.tags, tag) !== -1) {
ret = true;
}
});
} else {
// event has no tags at all..
ret = false;
}
}
// LEGEND FILTER
if (MED.filters.legend.length > 0) {
ev_icon = ev.icon;
if (_.indexOf(MED.filters.legend, ev_icon) == -1) {
// if it's not in the legend list
ret = false;
}
}
} // end if/else for custom filter function
/////////////
return ret;
},
/*
ADDING EVENTS ON INITIAL SWEEP!
invoked upon a fresh sweep of entire container, having added a set of ticks
--- occurs on expand/collapse
--- ticks are created afresh
*/
freshTimelines : function () {
var me = this,
t, i, tl, tlView, tlModel, tu, ts, tick, tE, tl_ht, t_f, t_l,
active = MED.activeTimelines,
ticks = MED.ticksArray,
borg = '',
$title, $ev, $tl,
evid, ev,
stuff = '',
cx = me.dimensions.container.centerx,
cw = me.dimensions.container.width,
foSec = MED.getFocusDate().sec,
zi = MED.getZoomInfo(),
spp = zi.spp,
zl = zi.level,
tickUnit = zi.unit,
tArr = [],
idArr = [],
// left and right scope
half = Math.floor(spp * (cw/2)),
lsec = foSec - half,
rsec = foSec + half,
tz_offset = 0, tbwidth = 0,
spanin,
legend_label = "",
spanins = [],
expCol, tl_top=0,
cht = me.dimensions.container.height,
ceiling = 0,
tl_min_bottom = MED.options.minimum_timeline_bottom,
ticks_ht = me.dimensions.ticks.height;
/////////////////////////////////////////
/*
var testDate = MED.getFocusDate();
var tdFocus = Math.floor(testDate.sec);
var tickSec = me.getTickSeconds['da'](testDate.rd);
*/
//////////////////////////////////////////
$.publish(container_name + ".viewer.rendering");
for (var a=0; a<active.length; a++) {
idArr = [];
// FOR EACH _ACTIVE_ TIMELINE...
tlModel = MED.timelineCollection.get(active[a]);
tl = tlModel.attributes;
tl.visibleEvents = [];
if (MED.viewMode == "timeline") {
expCol = tl.display;
// TODO establish the 120 below in some kind of constant!
// meanwhile: tl_top is the starting height of a loaded timeline
tl_bottom = (tl.bottom) ? stripPx(tl.bottom) : tl_min_bottom;
if (tl_bottom < tl_min_bottom) tl_bottom = tl_min_bottom;
tl_top = ticks_ht - tl_bottom;
tl_min_bottom = MED.options.minimum_timeline_bottom;
tlView = new tg.TG_TimelineView({model:tlModel});
tz_offset = MED.timeOffset.seconds / spp;
$tl = $(tlView.render().el).appendTo(TICKS);
var laneTops = {};
if (tlModel.get('hasLanes') && tlModel.get('useLanes')) {
// clear existing lanes
$(".tg-lane").remove();
var $lane, lane_top = 32, ltop;
_.each(tlModel.get('lanes'), function(lane) {
ltop = lane.top || (lane_top +=30);
$lane = $("<div class='tg-lane'>" + lane.title + "<div class='arrow-right'></div></div>");
$lane.appendTo($tl)
.draggable({
axis:"y",
stop:function(event, ui) {
lane.top = Math.abs($(event.target).position().top);
MED.refresh()
}
})
.css({"top": -1 * ltop, "border-left":"4px solid " + lane.color})
.find(".arrow-right").css({"border-left":"10px solid " + lane.color})
lane.top = laneTops[lane.id] = Math.abs(ltop);
});
}
$title = $tl.find(".titleBar");
// this is the individual (named) timeline, not the entire interface
// if a single timeline, set images to the bottom
var tbh = $title.outerHeight();
me.room = tl_top; // (cht - (Math.abs(tl_top) + tbh)) - (me.dimensions.footer.height + me.dimensions.tick.height);
$tl.draggable({
axis:"y",
handle:".titleBar",
stop: function () {
var posi = $(this).position();
// chrome doesn't allow access the new bottom
var new_bottom = (ticks_ht - stripPx($(this).css("top"))) -1;
if (new_bottom < tl_min_bottom) {
$(this).css("bottom", tl_min_bottom);
new_bottom = tl_min_bottom;
}
var tid = $(this).attr("id");
// if we've dragged the timeline up or down
// reset its .top value and refresh, mainly
// to reset ceiling (+/visible) properties
var tl = MED.timelineCollection.get(tid);
tl.set({bottom:new_bottom});
// if a single timeline, set images to the bottom
var tbh = $title.outerHeight();
me.room = me.dimensions.ticks.height - new_bottom;
MED.refresh();
}
})
.css({"bottom":tl_bottom, "left": tz_offset});
if (typeof tl.bounds != "undefined") {
t_f = cx + ((tl.bounds.first - foSec) / spp);
t_l = cx + ((tl.bounds.last - foSec) / spp);
} else {
// if no events, we have to make this up
t_f = cx;
t_l = cx + 300;
}
tbwidth = Math.floor(t_l - t_f);
var tmax = 1000000;
var farl = -1 * (tmax - 2000);
// browsers have a maximum width for divs before
// they konk out... if we get to a high point, we
// can truncate the div, but have to make sure to
// equally adjust the left position if the right
// end of the div is needing to be placed in-screen
// whew.
if (tbwidth > tmax) {
var dif = tbwidth - tmax;
tbwidth = tmax;
if (t_f < farl) {
t_f = t_f + dif;
}
}
$title.css({"top":tl_ht, "left":t_f, "width":tbwidth}).data({"lef":t_f, "wid":tbwidth});
/// for initial sweep display, setup fresh borg for organizing events
if (expCol == "expanded") { tl.borg = borg = new timeglider.TG_Org(); }
var tick;
//cycle through ticks for hashed events
for (var tx=0; tx<ticks.length; tx++) {
tick = ticks[tx];
tArr = this.getTimelineEventsByTick({tick:tick, timeline:tl});
idArr = _.union(idArr, tArr);
}
tl.visibleEvents = idArr;
// detect if there are boundless spans (bridging, no start/end points)
_.each(tl.spans, function (spanin) {
if (_.indexOf(idArr, spanin.id) === -1) {
if ((spanin.start < lsec && spanin.end > rsec)
|| (spanin.end < rsec && spanin.end > lsec)) {
// adds to beginning to prioritize
idArr.unshift(spanin.id);
tl.visibleEvents.push(spanin.id);
}
}
});
// clean out dupes with _.uniq
stuff = this.compileTickEventsAsHtml(tl, _.uniq(idArr), 0, "sweep", tickUnit);
// future planning for scrollable overflow
if (options.event_overflow == "scroll") {
ceiling = 10000;
} else {
//!TODO: does ANY timeline have an image lane??
if (tl.inverted) {
ceiling = tl_bottom - 16;
} else {
ceiling = (tl.hasImageLane || tg.mode == "authoring") ? (tl_top - MED.image_lane_height) - me.singleTitleHeight : tl_top - me.singleTitleHeight ;
}
}
var onIZoom = (tl.initial_zoom == MED.getZoomLevel());
if (expCol == "expanded") {
stuff = borg.getHTML({tickScope:"sweep", ceiling:ceiling, onIZoom:onIZoom, inverted:tl.inverted, laneTops:laneTops});
tl.borg = borg.getBorg();
}
if (stuff != "undefined") {
$tl.append(stuff.html);
me.adjustScroller(stuff.high, stuff.low);
}
setTimeout( function() {
me.registerEventImages($tl);
}, 100);
////////////
/////////////////////
// MOBILE LIST MODE
/////////////////////
////////////
} else if (MED.viewMode == "list") {
// CREATE "hideTimelineControls"
/*
$(".tg-footer-center").hide();
$(".timeglider-slider-container").hide();
$(".tg-image-lane-pull").hide();
$(".timeglider-truck").hide();
$(".timeglider-centerline").hide();
*/
// WIPE OUT ENTIRE ELEMENT EACH TIME
$("#tg-list-wrapper").remove();
// AND RECREATE
var $tg_list_container = $("<div id='tg-list-wrapper'><div id='tg-list' class='tg-list-events'><ul id='list-events-ul' class='list-ul'></ul></div></div>").prependTo(CONTAINER);
// remember, this is inside a or loop!
var hash = MED.eventCollection.getTimelineHash(tlModel.get("id"));
var evts = [];
_.each(hash.all, function(evid) {
ev = MED.eventCollection.get(evid).attributes;
// with list, ignore zoom level
if (me.passesFilters(ev, 0) === true) {
evts.push(ev);
}
});
// change this to TG_OrgList
var borg = new timeglider.TG_OrgList(evts, MED);
stuff = borg.getHTML();
// EVERYTHING ADDED HERE
if (stuff != "undefined") { $(".tg-list-events ul").append(stuff.html); }
var testing_touch = true;
if (CLICKORTOUCH == "touchstart" || testing_touch) {
tg.listIScroll = new iScroll('tg-list-wrapper');
} else {
// apply jscrollpane to list ?
if ($.fn.jScrollPane) {
$(".tg-list-events").jScrollPane();
}
tg.listIScroll = {animating:false};
}
setTimeout(function() {
$(".tg-legend .tg-legend-close").trigger(CLICKORTOUCH);
}, 300);
// use click even with mobile view
$(".tg-list-li").bind("click", function() {
if (tg.listIScroll.distance > 20 || tg.listIScroll.moved) return false;
var $ev = $(this);
var evid = $ev.attr("id");
me.eventModal(evid, $ev);
});
} // end list/timeline if
}// end for each timeline
// initial title shift since it's not on-drag
me.registerTitles();
setTimeout(function () {
me.applyFilterActions();
MED.setInitialScope();
}, 300);
$.publish(container_name + ".viewer.rendered");
}, // ends freshTimelines()
/*
* appendTimelines
* @param tick {Object} contains serial, time-unit, and more info
*/
appendTimelines : function (tick, side) {
var active = MED.activeTimelines,
idArr = [],
tModel, $tl, tl, f,
stuff = "", diff = 0,
ceiling = 0,
me = this;
$.publish(container_name + ".viewer.rendering");
for (var a=0; a<active.length; a++) {
tModel = MED.timelineCollection.get(active[a]);
tl = tModel.attributes;
// get the events from timeline model hash
idArr = this.getTimelineEventsByTick({tick:tick, timeline:tl});
tl.visibleEvents = _.union(tl.visibleEvents, idArr);
tl_top = (tl.top) ? stripPx(tl.top) : (me.initTimelineVOffset);
// we need to see if the right end of a long span
// is present in the newly added tick
if (side == "left") {
_.each(tl.spans, function (spanin) {
//var diff = tick.seconds.start - spanin.end;
if (spanin.end < tick.seconds.end && spanin.end > tick.seconds.start) {
//not already in array
if (_.indexOf(tl.visibleEvents, spanin.id) === -1) {
// add to beginning to prioritize
idArr.unshift(spanin.id);
tl.visibleEvents.push(spanin.id);
}
}
});
}
var laneTops = {};
if (tModel.get('hasLanes') && tModel.get('useLanes')) {
_.each(tModel.get('lanes'), function(lane) {
laneTops[lane.id] = Math.abs(lane.top);
});
}
// this either puts it into the timeline's borg object
// or, if compressed, creates HTML for compressed version.
// stuff here would be null if expanded...
stuff = this.compileTickEventsAsHtml(tl, idArr, tick.serial, "append", tick.unit);
// TODO: make 56 below part of layout constants collection
if (options.event_overflow == "scroll") {
ceiling = 10000;
} else {
ceiling = (tl.hasImageLane) ? (tl_top - MED.image_lane_height) - me.singleTitleHeight : tl_top;
}
var onIZoom = (tl.initial_zoom == MED.getZoomLevel());
// borg it if it's expanded.
if (tl.display == "expanded"){
// tl.top is the ceiling
stuff = tl.borg.getHTML({tickScope:tick.serial, ceiling:ceiling, onIZoom:onIZoom, inverted:tl.inverted, laneTops:laneTops});
}
var $vu = $(CONTAINER + " .tg-timeline-envelope#" + tl.id);
$vu.append(stuff.html);
me.adjustScroller(stuff.low, stuff.high);
this.registerEventImages($tl);
} // end for() in active timelines
// this needs to be delayed because the append usually
// happens while dragging, which already brings the
// browser to the processor limits; make timeout time
// below larger if things are crashing : )
setTimeout(function () { me.applyFilterActions(); }, 500);
$.publish(container_name + ".viewer.rendered");
}, // end appendTimelines()
// events array, MED, tl, borg,
// "sweep" vs tick.serial (or fresh/append)
/*
*
* @param btype {String} "sweep" || "append"
*
*
*/
compileTickEventsAsHtml : function (tl, idArr, tick_serial, btype, tickUnit) {
var me=this,
posx = 0,
cx = this.dimensions.container.centerx,
expCol = tl.display,
ht = 0,
stuff = "",
foSec = MED.startSec,
zi = MED.getZoomInfo(),
spp = zi.spp,
zl = zi.level,
buffer = 16,
img_ht = 0,
img_wi = 0,
borg = tl.borg || "",
ev = {},
font_ht = 0,
shape = {},
colTop = 0,
impq,
block_arg = "sweep"; // default for initial load
if (borg) tl.borg.clearFresh();
var isBig = function(tu) {
if (tu == "da" || tu == "mo" || tu == "ye" || tu == "de" || tu == "ce" || tu == "thou"){
return false;
} else {
return true;
}
};
if (btype == "append") {
block_arg = tick_serial;
}
for (var i=0; i<idArr.length; i++) {
// BBONE
ev = MED.eventCollection.get(idArr[i]).attributes;
if (this.passesFilters(ev, zl) === true) {
// the larger units (>=thou) have have an error
// in their placement from long calculations;
// we can compensate for them here...
var adjust = (isBig(tickUnit)) ? .99795 : 1;
var ev_sds = ev.startdateObj.sec * adjust;
posx = cx + ((ev_sds - foSec) / spp);
if (expCol == "expanded") {
impq = (tl.size_importance === true || tl.size_importance === 1) ? tg.scaleToImportance(ev.importance, zl) : 1;
ev.width = (ev.titleWidth * impq) + buffer;
ev.fontsize = MED.base_font_size * impq;
ev.left = posx;
ev.spanwidth = 0;
if (ev.span == true) {
ev.spanwidth = ((ev.enddateObj.sec - ev.startdateObj.sec) / spp);
if (ev.spanwidth > ev.width) { ev.width = ev.spanwidth + buffer; }
}
img_ht = 0;
font_ht = Math.ceil(ev.fontsize);
ev.height = ev.fixed_height || (font_ht + 4);
ev.top = ht - ev.height;
ev.bottom = 0;
if (ev.image && ev.image.display_class == "inline") {
var img_scale = (ev.image.scale || 100) / 100;
img_ht = (img_scale * ev.image.height) + 2;
img_wi = (img_scale * ev.image.width) + 2;
// !TODO
// THIS NEEDS TO BE REVERSABLE WITH POLARITY
// WORKS ONLY WITH BOTTOM-UP CURRENTLY
ev.shape = {
"img_ht":img_ht,
"img_wi":img_wi,
"title": "shape",
"top": (ev.top - (img_ht + 8)),
"bottom": ev.bottom,
"left": ev.left,
"right":ev.left + (img_wi + 8)
};
} else {
ev.shape = "";
}
// block_arg is either "sweep" for existing ticks
// or the serial number of the tick being added by dragging
borg.addBlock(ev, block_arg);
// end expanded state
} else if (expCol == "collapsed") {
if (tl.inverted) {
colTop = 4;
} else {
colTop = ht - 20;
}
colIcon = (ev.icon) ? tg.icon_folder + ev.icon: tg.icon_folder + "shapes/circle_white.png";
stuff += "<div id='" + ev.id +
"' class='timeglider-timeline-event tg-event-collapsed' style='top:" +
colTop + "px;left:" + posx + "px'><img src='" + colIcon + "'></div>";
}
} // end if it passes filters
}
if (expCol == "collapsed") {
return {html:stuff};
} else {
// if expanded, "stuff" is
// being built into the borg
return "";
}
},
/*
* registerEventImages
* Events can have classes applied to their images; these routines
* take care of doing non-css-driven positioning after the layout
* has finished placing events in the tick sphere.
*
*
*/
registerEventImages : function ($timeline) {
var me = this,
laneHt = MED.image_lane_height,
padding = 4,
laneMax = 400,
stht = this.singleTitleHeight;
if (laneHt > laneMax) { laneHt = laneMax; }
$(CONTAINER + " .timeglider-event-image-lane").each(
function () {
var $div = $(this),
imgHt = laneHt - (padding/2),
$img = $(this).find("img"),
imax = parseInt($div.data("max_height"), 10) || laneMax;
if (imax < imgHt) {
imgHt = imax;
}
if (imgHt > 10) {
$div.css({"display":"block"})
.position({
my: "center top+" + (stht + padding),
at: "center top",
of: $(CONTAINER)
})
.css({left:0});
$img.css("height", imgHt - (padding));
} else {
$div.css({"display":"none"});
}
}
);
},
applyFilterActions: function() {
var fa = MED.filterActions,
collection = MED.eventCollection.models,
ev_id;
if (!_.isEmpty(fa)) {
// For performance reasons, having just
// one filter function is probably smart : )
_.each(fa, function (f) {
// filter:actionFilter, fn:actionFunction
_.each(collection, function (ev) {
if (f.filter(ev)) {
ev_id = ev.get("id");
// it's passed the filter, so run it through
// the action function
f.fn($(".timeglider-timeline-event#" + ev_id));
}
});
})
}
},
expandCollapseTimeline : function (id) {
var tl = MED.timelineCollection.get(id).attributes;
if (tl.display == "expanded") {
tl.display = "collapsed";
} else {
tl.display = "expanded";
}
MED.refresh();
},
invertTimeline : function (id) {
var tl = MED.timelineCollection.get(id).attributes;
if (tl.inverted == false) {
tl.inverted = true;
} else {
tl.inverted = false;
}
MED.refresh();
},
//////// MODALS
timelineModal : function (id) {
$(".tg-timeline-modal").remove();
var me = this,
tl = MED.timelineCollection.get(id),
$modal;
if (tl.get("description")) {
var ch = me.dimensions.container.height,
modal = new this.timelineInfoModal({model:tl});
var hh = $(".tg-widget-header").outerHeight() + 32;
var tgcht = $(".timeglider-container").position();
$modal = $(modal.render().el)
.appendTo("body")
.position({
my: "left+16 top+39",
at: "left top",
of: $(".timeglider-container"),
collision: "none none"
})
.css({"z-index":me.ztop++, "max-height":ch-64});
if (MED.singleTimelineID) {
$modal.find("h4").hide();
}
if ($.fn.jScrollPane) {
$(".jscroll").jScrollPane();
}
}
},
/*
* Generates a horizontal menu of all links
* in the event's link_json array
*/
createEventLinksMenu : function (linkage) {
if (!linkage) return "";
var html = '', l = 0, lUrl = "", lLab="";
if (typeof(linkage) == "string") {
// single url string for link: use "link"
html = "<li><a href='" + linkage + "' target='_blank'>link</a></li>"
} else if (typeof(linkage) == "object"){
// array of links with labels and urls
for (l=0; l<linkage.length; l++) {
lUrl = linkage[l].url;
lLab = linkage[l].label;
html += "<li><a href='" + lUrl + "' target='_blank'>" + lLab + "</a></li>"
}
}
return html;
},
eventModal : function (eid, $event) {
// remove if same event already has modal opened
$.publish(container_name + ".viewer.eventModal");
// this removes a duplicate modal
$(CONTAINER + " #" + eid + "_modal").remove();
var me = this,
map_view = false,
video_view=false,
map = "", map_options = {}, $modal, llar=[], mapZoom = 0,
ev = MED.eventCollection.get(eid).attributes,
// modal type: first check event, then timeline-wide option
modal_type = ev.modal_type || options.event_modal.type;
var ev_img = (ev.image && ev.image.src) ? "<img src='" + ev.image.src + "'>" : "",
links = this.createEventLinksMenu(ev.link),
templ_obj = {
id:ev.id,
title:ev.title,
description:ev.description,
link:ev.link,
dateline: me.getEventDateLine(ev),
links:links,
image:ev_img
}
if (ev.video) {
templ_obj.video = ev.video;
modal_type = "full";
video_view = true;
templ_obj.video = ev.video;
} else if (ev.map && ev.map.latlong) {
map_view = true;
modal_type = "full";
// if the embed size is small
} else if ((ev.description.length > 1200) || (me.dimensions.container.width < 500)) {
modal_type = "full";
}
// return false;
switch (modal_type) {
case "full":
$modal = $.tmpl(me._templates.event_modal_full,templ_obj);
// full modal with scrim, etc
var pad = 32;
$modal
.appendTo(CONTAINER)
.position({
my: "left top",
at: "left top",
of: (CONTAINER),
collision: "none none"
});
var ch = me.dimensions.container.height;
var cw = me.dimensions.container.width;
var $panel = $modal.find(".tg-full_modal_panel");
var pw = cw - 64;
var ph = ch - 64;
var iw = 0;
$panel.css({
"width":pw,
"height":ph,
"top":"32px",
"left":"32px"
});
var $pp = $(".tg-full_modal-body");
var pph = ph-120;
if (map_view == true) {
// REPLACE WITH OPEN STREET MAPS / LEAFLET
//////////////////////////////////////////
$map = $("<div id='map_modal_map' class='tg-modal_map'></div>").prependTo($pp);
mapZoom = ev.map.zoom || 12;
var llarr = String(ev.map.latlong).split(",");
var map_ll = new google.maps.LatLng(parseFloat(llarr[0]), parseFloat(llarr[1]));
map_options = {
zoom:mapZoom,
center: map_ll,
mapTypeId: google.maps.MapTypeId.ROADMAP
}
map = new google.maps.Map($("#map_modal_map")[0], map_options);
// if there are markers provided in the map:
if (ev.map.markers) {
for (var i=0; i<ev.map.markers.length; i++) {
var marker = ev.map.markers[i];
var image = new google.maps.MarkerImage(marker.image,
new google.maps.Size(24, 32),
new google.maps.Point(0,0),
new google.maps.Point(0, 32)); // "plant" origin is lower left
var loc = marker.latlong.split(",");
var llobj = new google.maps.LatLng(loc[0], loc[1]);
var marker = new google.maps.Marker({
position: llobj,
map: map,
icon: marker.icon,
title: marker.title,
zIndex:marker.zIndex
});
}
}
} else if (video_view == true) {
$vid = $("<div class='tg-modal-video'><iframe frameborder='0' src='" + ev.video + "'></iframe></div>").appendTo(".tg-full_modal-vidimg");
if (ph > 450) {
$vid.find("iframe").css("height", $vid.width() * .66);
} else {
var mini_h = ph - 80;
var mini_w = mini_h * 1.5;
$vid.find("iframe").css({"float":"left", "height":mini_h, "width":mini_w});
}
}
if (ev.image) {
var $img = $(".tg-full_modal-body img");
var img_max_ht = ph-110;
if (ev.image.height > img_max_ht) {
$img.css("height", img_max_ht);
} else if (ev.image.width < pw/3) {
// small image
iw = ev.image.width;
$img.css("width", iw);
}
}
if ($pp.height() > pph-16) {
$pp.css({"height":pph-16, "overflow-y":"scroll"});
}
break;
case "link-iframe":
// show the link (i.e. Wikipedia, etc) in an iframe
$modal = $.tmpl(me._templates.event_modal_iframe,templ_obj);
$modal
.appendTo(TICKS)
.css("z-index", me.ztop++)
.position({
my: "center top+32",
at: "center top",
of: $(CONTAINER),
collision: "flip fit"
})
.hover(function () { $(this).css("z-index", me.ztop++); });
break;
case "custom":
break;
default:
templ_obj.extra_class = (templ_obj.image) ? "has-image":"no-image";
// .appendTo(CONTAINER);
// works, but modal does not follow drag
$modal = $.tmpl(me._templates.event_modal_small,templ_obj).appendTo(me._views.TICKS);
var pad = 8,
arrow_class = "",
tb_class = "",
lr_class = "",
ev_left = "",
ev_top = $event.position().top,
ev_off = $event.offset();
var co_ht = me.dimensions.container.height;
var co_wi = me.dimensions.container.width;
var co_off = me.dimensions.container.offset;
var modal_ht = $modal.outerHeight();
var modal_wi = $modal.outerWidth();
// compensate for height of timeline envelope
var env_top = $event.closest(".tg-timeline-envelope").position().top;
var space_above = (env_top + ev_top) - 28;
var space_below = Math.abs(ev_top);
var pos_my = "", pos_at = "";
var ev_rel = ev_off.left - co_off.left;
var farthest = me.dimensions.container.width - (modal_wi + pad);
if (ev_rel < pad) {
// shift to the left
ev_left = "+" + String(pad + (ev_rel * -1));
} else if (ev_rel > farthest) {
// it's too far off to the right
// find amount that $modal width
// plus event offset_left exceeds
// container width!
var leftward = co_wi - ev_rel;
var exc = modal_wi - leftward;
ev_left = "-" + (pad * 4 + exc);
}
if (space_above > modal_ht + 12) {
// position above
pos_my = "left"+ev_left+" bottom-12";
pos_at = "left top";
} else if (space_below > modal_ht){
// position modal below the event
pos_my = "left"+ev_left+" top+8";
pos_at = "left bottom";
} else {
pos_my = "center"+ev_left+" center";
pos_at = "center center";
}
$modal
.css({"z-index": me.ztop++})
.position({
my: pos_my,
at: pos_at,
of: $event,
collision: "flip flip"
});
} // eof switch
if ($.fn.jScrollPane) {
$(".jscroll").jScrollPane();
}
}, // eof eventModal
legendModal : function (id) {
var me=this;
var leg = [];
if (id=="pres" || timeglider.mode == "presentation") {
if (typeof MED.presentation.legend == "object") {
leg = MED.presentation.legend;
}
} else {
leg = MED.timelineCollection.get(id).attributes.legend;
}
var html = "",
i_sel = "",
initClass = "";
if (options.legend.type === "checkboxes") {
initClass = "tg-legend-item tg-legend-icon-selected";
} else {
initClass = "tg-legend-item";
}
_.each(leg, function(l){
html += "<li class='" + initClass + "'><img class='tg-legend-icon' src='" + options.icon_folder + l.icon + "'><span class='legend-info'>" + l.title + "</span></li>";
});
var templ_obj = {id:id, legend_list:html};
// remove existing legend
$(CONTAINER + " .tg-legend").remove();
$.tmpl(me._templates.legend_modal,templ_obj)
.appendTo(CONTAINER)
.css("z-index", me.ztop++)
.toggleClass("tg-display-none")
.position({
my: "right-64 top+38",
at: "right top",
of: $(CONTAINER),
collision: "none"
});
i_sel = CONTAINER + " .legend-info, " + CONTAINER + " .tg-legend-item";
$(CONTAINER).delegate(".tg-legend-item", "mouseup", function(e) {
var $legend_item = $(this);
var icon = ($legend_item.children("img").attr("src"));
$legend_item.toggleClass("tg-legend-icon-selected");
MED.setFilters({origin:"legend", icon: icon});
});
// selects all legend items so that
// only legend-related items are showing on
// the stage
if (options.legend.type === "checkboxes") {
setTimeout(function() {
$(".tg-legend-all").trigger("click");
}, 200);
}
},
parseHTMLTable : function(table_id) {
var obj = {},
now = +new Date(),
keys = [];
$('#' + table_id).find('tr').each(function(i){
////////// each..
var children = $(this).children(),
row_obj;
if ( i === 0 ) {
keys = children.map(function(){
return $(this).attr( 'class' ).replace( /^.*?\btg-(\S+)\b.*?$/, '$1' );
}).get();
} else {
row_obj = {};
children.each(function(i){
row_obj[ keys[i] ] = $(this).text();
});
obj[ 'prefix' + now++ ] = row_obj;
}
/////////
});
return obj;
}
} // end VIEW prototype
tg.TG_TimelineView = Backbone.View.extend({
initialize: function (t) {
var me=this;
this.mediator = t.model.get("mediator");
this.model.bind('change:title', function () {
$(me.el).find(".timeline-title-span").text(me.model.get("title"));
});
if (this.mediator.timelineCollection.length > 1 || tg.mode == "authoring") {
this.titleBar = "fullBar";
} else {
this.titleBar = "hiddenBar";
}
this.model.bind('destroy', this.remove, this);
},
tagName: "div",
events: {
"click .timeline-title-span" : "titleClick"
},
className: "tg-timeline-envelope clearfix",
getTemplate: function() {
var tmpl = "",
env_bts = "",
env_b = "",
inverted = "";
if (this.model.get("inverted")) {
inverted = " timeline-inverted";
} else {
inverted = "";
}
debug.log("titlebar:", this.titleBar);
if (this.titleBar == "fullBar") {
tmpl = "<div class='titleBar'>"
+ "<div class='timeline-title" + inverted + "'>"
+ "<span class='timeline-title-span'>";
env_bts = "<div class='tg-env-buttons'>";
// INFO BUTTON
if (this.model.get("description")) {
env_bts += "<div class='tg-env-button tg-env-info timeline-info-bt' data-timeline_id='${id}'></div>";
}
// LEGEND BUTTON
if (this.model.get("hasLegend")) {
env_bts += "<div class='tg-env-button tg-env-legend tg-legend-bt' data-timeline_id='${id}'></div>";
}
// INVERT BUTTON
env_bts += "<div class='tg-env-button tg-env-invert tg-invert-bt' data-timeline_id='${id}'></div>";
// EXPAND BUTTON
env_bts += "<div class='tg-env-button tg-env-expcol tg-expcol-bt' data-timeline_id='${id}'></div>";
env_bts += "</div>";
// env_b = (timeglider.mode == "preview" || timeglider.mode == "publish") ? env_bts : "";
tmpl += env_bts + "${title}</span></div></div>";
} else if (this.titleBar == "imageBar") {
tmpl = "<div class='titleBar imageBar'></div>";
} else {
tmpl = "<div class='titleBar tg-display-none'></div>";
}
return tmpl;
},
render: function() {
var me = this;
var id = me.model.get("id");
var title = me.model.get("title");
var _template = me.getTemplate();
var state_class = this.model.get("inverted") ? "inverted" : "straight-up";
$(this.el)
.html($.tmpl(_template, this.model.attributes))
.attr("id", this.model.get("id"))
.addClass(state_class);
return this;
},
setText: function() {
/*
var text = this.model.get('text');
this.$('.todo-text').text(text);
this.input = this.$('.todo-input');
*/
},
titleClick: function() {
MED.timelineTitleClick(this.model.get("id"));
},
remove: function() {
$(this.el).remove();
}
//clear: function() {
// this.model.destroy();
//}
});
/*
zoomTree
****************
there's no zoom level of 0, so we create an empty element @ 0
This could eventually be a more flexible system so that a 1-100
value-scale could apply not to "5 hours to 10 billion years", but
rather to 1 month to 10 years. For now, it's static according to
a "universal" system.
*/
tg.zoomTree = [
{},
{unit:"da", width:35000,level:1, label:"30 minutes"},
{unit:"da", width:17600,level:2, label:"1 hour"},
{unit:"da", width:8800,level:3, label:"2 hours"},
{unit:"da", width:4400,level:4, label:"5 hours"},
{unit:"da", width:2200, level:5, label:"10 hours"},
{unit:"da", width:1100, level:6, label:"1 DAY"},
{unit:"da", width:550, level:7, label:"40 hours"},
{unit:"da", width:432, level:8, label:"2 days"},
{unit:"da", width:343, level:9, label:"2.5 days"},
{unit:"da", width:272, level:10, label:"3 days"},
{unit:"da", width:216, level:11, label:"4 days"},
{unit:"da", width:171, level:12, label:"5 days"},
{unit:"da", width:136, level:13, label:"1 WEEK"},
{unit:"da", width:108, level:14, label:"8 days"},
/* 108 * 30 = equiv to a 3240 month */
{unit:"mo", width:2509, level:15, label:"10 days"},
{unit:"mo", width:1945, level:16, label:"2 WEEKS"},
{unit:"mo", width:1508, level:17, label:"18 days"},
{unit:"mo", width:1169, level:18, label:"3 weeks"},
{unit:"mo", width:913, level:19, label:"1 MONTH"},
{unit:"mo", width:719, level:20, label:"5 weeks"},
{unit:"mo", width:566, level:21, label:"6 weeks"},
{unit:"mo", width:453, level:22, label:"2 MONTHS"},
{unit:"mo", width:362, level:23, label:"10 weeks"},
{unit:"mo", width:290, level:24, label:"3 MONTHS"},
{unit:"mo", width:232, level:25, label:"4 months"},
{unit:"mo", width:186, level:26, label:"5 months"},
{unit:"mo", width:148, level:27, label:"6 MONTHS"},
{unit:"mo", width:119, level:28, label:"7 months"},
{unit:"mo", width:95, level:29, label:"9 months"},
{unit:"mo", width:76, level:30, label:"1 YEAR"},
/* 76 * 12 = equiv to a 912 year */
{unit:"ye", width:723, level:31, label:"15 months"},
{unit:"ye", width:573, level:32, label:"18 months"},
{unit:"ye", width:455, level:33, label:"2 YEARS"},
{unit:"ye", width:361, level:34, label:"2.5 years"},
{unit:"ye", width:286, level:35, label:"3 years"},
{unit:"ye", width:227, level:36, label:"4 years"},
{unit:"ye", width:179, level:37, label:"5 years"},
{unit:"ye", width:142, level:38, label:"6 years"},
{unit:"ye", width:113, level:39, label:"8 years"},
{unit:"ye", width:89, level:40, label:"10 years"},
{unit:"de", width:705, level:41, label:"13 years"},
{unit:"de", width:559, level:42, label:"16 years"},
{unit:"de", width:443, level:43, label:"20 years"},
{unit:"de", width:302, level:44, label:"25 years"},
{unit:"de", width:240, level:45, label:"30 years"},
{unit:"de", width:190, level:46, label:"40 years"},
{unit:"de", width:150, level:47, label:"50 years"},
{unit:"de", width:120, level:48, label:"65 years"},
{unit:"de", width:95, level:49, label:"80 years"},
{unit:"de", width:76, level:50, label:"100 YEARS"},
{unit:"ce", width:600, level:51, label:"130 years"},
{unit:"ce", width:480, level:52, label:"160 years"},
{unit:"ce", width:381, level:53, label:"200 YEARS"},
{unit:"ce", width:302, level:54, label:"250 years"},
{unit:"ce", width:240, level:55, label:"300 years"},
{unit:"ce", width:190, level:56, label:"400 years"},
{unit:"ce", width:150, level:57, label:"500 YEARS"},
{unit:"ce", width:120, level:58, label:"600 years"},
{unit:"ce", width:95, level:59, label:"1000 YEARS"},
{unit:"ce", width:76, level:60, label:"1100 years"},
{unit:"thou", width:603, level:61, label:"1500 years"},
{unit:"thou", width:478, level:62, label:"2000 years"},
{unit:"thou", width:379, level:63, label:"2500 years"},
{unit:"thou", width:301, level:64, label:"3000 years"},
{unit:"thou", width:239, level:65, label:"4000 years"},
{unit:"thou", width:190, level:66, label:"5000 YEARS"},
{unit:"thou", width:150, level:67, label:"6000 years"},
{unit:"thou", width:120, level:68, label:"7500 years"},
{unit:"thou", width:95, level:69, label:"10,000 YEARS"},
{unit:"thou", width:76, level:70, label:"12,000 years"},
{unit:"tenthou", width:603, level:71, label:"15,000 years"},
{unit:"tenthou", width:358, level:72, label:"25,000 years"},
{unit:"tenthou", width:213, level:73, label:"40,000 years"},
{unit:"tenthou", width:126, level:74, label:"70,000 years"},
{unit:"tenthou", width:76, level:75, label:"100,000 YEARS"},
{unit:"hundredthou", width:603, level:76, label:"150,000 years"},
{unit:"hundredthou", width:358, level:77, label:"250,000 years"},
{unit:"hundredthou", width:213, level:78, label:"400,000 years"},
{unit:"hundredthou", width:126, level:79, label:"700,000 years"},
{unit:"hundredthou", width:76, level:80, label:"1 million years"},
{unit:"mill", width:603, level:81, label:"1.5 million years"},
{unit:"mill", width:358, level:82, label:"3 million years"},
{unit:"mill", width:213, level:83, label:"4 million years"},
{unit:"mill", width:126, level:84, label:"6 million years"},
{unit:"mill", width:76, level:85, label:"10 million years"},
{unit:"tenmill", width:603, level:86, label:"15 million years"},
{unit:"tenmill", width:358, level:87, label:"25 million years"},
{unit:"tenmill", width:213, level:88, label:"40 million years"},
{unit:"tenmill", width:126, level:89, label:"70 million years"},
{unit:"tenmill", width:76, level:90, label:"100 million years"},
{unit:"hundredmill", width:603, level:91, label:"120 million years"},
{unit:"hundredmill", width:358, level:92, label:"200 million years"},
{unit:"hundredmill", width:213, level:93, label:"300 million years"},
{unit:"hundredmill", width:126, level:94, label:"500 million years"},
{unit:"hundredmill", width:76, level:95, label:"1 billion years"},
{unit:"bill", width:603, level:96, label:"15 million years"},
{unit:"bill", width:358, level:97, label:"30 million years"},
{unit:"bill", width:213, level:98, label:"50 million years"},
{unit:"bill", width:126, level:99, label:"80 million years"},
{unit:"bill", width:76, level:100, label:"100 billion years"}
];
// immediately invokes to create extra information in zoom tree
//
tg.calculateSecPerPx = function (zt) {
for (var z=1; z<zt.length; z++) {
var zl = zt[z];
var sec = 0;
switch(zl.unit) {
case "da": sec = 86400; break;
case "mo": sec = 2419200; break; // assumes only 28 days per
case "ye": sec = 31536000; break;
case "de": sec = 315360000; break;
case "ce": sec = 3153600000; break;
case "thou": sec = 31536000000; break;
case "tenthou": sec = 315360000000; break;
case "hundredthou": sec = 3153600000000; break;
case "mill": sec = 31536000000000; break;
case "tenmill": sec = 315360000000000; break;
case "hundredmill": sec = 3153600000000000; break;
case "bill": sec = 31536000000000000; break;
}
// generate hash for seconds per pixel
zl.spp = Math.round(sec / parseInt(zl.width));
}
// call it right away to establish values
}(tg.zoomTree); // end of zoomTree
/* a div with id of "hiddenDiv" has to be pre-loaded */
tg.getStringWidth = function (str) {
var $ms = $("#timeglider-measure-span");
$ms.css("font-size", MED.base_font_size);
if (str) {
// for good measure, make it a touch larger
return $ms.html(str).width() + MED.base_font_size;
} else {
return false;
}
};
tg.scaleToImportance = function(imp, zoom_level) {
// flash version: return ((importance - zoomLev) * 4.5) + 100;
// 100 being 1:1 or 12 px
// first basic version: return imp / zoo;
return (((imp - zoom_level) * 4.5) + 100) / 100;
},
String.prototype.removeWhitespace = function () {
var rg = new RegExp( "\\n", "g" )
return this.replace(rg, "");
}
if (debug) {
// adding a screen display for anything needed
debug.trace = function (stuff, goes) {
$("#" + goes).text(stuff);
}
}
tg.googleMapsInit = function () {
// debug.log("initializing google maps...")
}
tg.googleMapsLoaded = false;
tg.googleMapsLoad = function () {
if (tg.googleMapsLoaded == false) {
var script = document.createElement('script');
script.type = 'text/javascript';
script.src = 'http://maps.googleapis.com/maps/api/js?sensor=false&' +
'callback=timeglider.googleMapsInit';
document.body.appendChild(script);
tg.googleMapsLoaded = true;
}
}
})(timeglider);