/slippy-chart.js
JavaScript | 3341 lines | 2458 code | 532 blank | 351 comment | 309 complexity | 15df4ed905aea099527e72a5ccd3b5b0 MD5 | raw file
Large files files are truncated, but you can click here to view the full file
- /*!
- * Slippy Chart Library v0.1
- * http://www.scaledlabs.com
- *
- * Copyright (c) 2009 Kelvin Kakugawa
- *
- */
- /*jslint browser: true, debug: true, rhino: true*/
- /*extern jQuery*/
- (function($) {
- var
- window = this,
- document = window.document,
- undefined,
- _SlippyChart = window.SlippyChart,
- _$c = window.$c,
-
- $c = window.$c = SlippyChart = window.SlippyChart = {};
- // ========== Compatibility ==========
- // ---------- Event Handling ----------
- var addEvent = function(el, type, fn) {
- if (window.attachEvent) {
- addEvent = function(el, type, fn) {
- el.attachEvent("on"+type, fn);
- };
- } else {
- addEvent = function(el, type, fn) {
- if (type =="mousewheel") {
- type = "DOMMouseScroll";
- }
- el.addEventListener(type, fn, false);
- };
- }
- addEvent(el, type, fn);
- };
- ;;; $c.addEvent = addEvent;
- var removeEvent = function(el, type, fn) {
- if (window.detachEvent) {
- removeEvent = function(el, type, fn) {
- el.detachEvent("on"+type, fn);
- };
- } else {
- removeEvent = function(el, type, fn) {
- el.removeEventListener(type, fn, false);
- };
- }
- removeEvent(el, type, fn);
- };
- ;;; $c.removeEvent = removeEvent;
- var cancelEvent = function(e) {
- e = e ? e : window.event;
- if (e.stopPropagation) {
- e.stopPropagation();
- }
- if (e.preventDefault) {
- e.preventDefault();
- }
- e["cancelBubble"] = true;
- e["cancel"] = true;
- e["returnValue"] = false;
- return false; // note: exception for window.status
- };
- ;;; $c.cancelEvent = cancelEvent;
- // ========== Utilities ==========
- // ---------- Functional ----------
- var _map = function(func_, list_) {
- var len = list_.length || 0;
- var res = [];
- res.length = len;
- for (var i=len-1; i>=0; i--) {
- res[i] = func_(list_[i]);
- }
- return res;
- };
- ;;; $c._map = _map;
- var _reduce = function(func_, list_, initial_) {
- var len = list_.length || 0;
- if (len === 0) {
- return initial_;
- }
- var i = 0;
- if (typeof initial_ === "undefined") {
- initial_ = list_[i++];
- }
- for (; i<len; i++) {
- initial_ = func_(initial_, list_[i]);
- }
- return initial_;
- };
- ;;; $c._reduce = _reduce;
- var _filter = function(func_, list_) {
- var len = list_.length || 0;
- var res = [];
- for (var i=len-1; i>=0; i--) {
- if (func_(list_[i])) {
- res.push(list_[i]);
- }
- }
- return res.reverse();
- };
- ;;; $c._filter = _filter;
- var _zip = function() {
- var num_lists = arguments.length || 0;
- if (num_lists === 0) {
- return [];
- }
- var len = arguments[0].length || 0;
- var res = [];
- res.length = len;
- for (var i=len-1; i>=0; i--) {
- res[i] = _map(function (x) { return x[i];}, arguments);
- }
- return res;
- };
- ;;; $c._zip = _zip;
- // ---------- Binary Search ----------
- var _search = function(bucket_, key_) {
- var low = 0;
- var high = bucket_.length-1;
- while (low <= high) {
- var mid = Math.floor((low + high) / 2);
- var midVal = bucket_[mid];
- if (midVal < key_) {
- low = mid + 1;
- } else if (midVal > key_) {
- high = mid - 1;
- } else { // midVal == key_
- return mid;
- }
- }
- return -(low+1); // flag: key not found
- };
- ;;; $c._search = _search;
- var _range_r = function(bucket_, key_) {
- var low = 0;
- var high = bucket_.length-1;
- while (low <= high) {
- var mid = Math.floor((low + high) / 2);
- var midVal = bucket_[mid];
- if (midVal < key_) {
- low = mid + 1;
- } else if (midVal > key_) {
- high = mid - 1;
- } else { // midVal == key_
- return bucket_.slice(mid);
- }
- }
- return bucket_.slice(low);
- };
- ;;; $c._range_r = _range_r;
- var _range_l = function(bucket_, key_) {
- var low = 0;
- var high = bucket_.length-1;
- while (low <= high) {
- var mid = Math.floor((low + high) / 2);
- var midVal = bucket_[mid];
- if (midVal < key_) {
- low = mid + 1;
- } else if (midVal > key_) {
- high = mid - 1;
- } else { // midVal == key_
- return bucket_.slice(0, mid+1);
- }
- }
- return bucket_.slice(0, high+1);
- };
- ;;; $c._range_l = _range_l;
- var _range = function(bucket_, lkey_, rkey_) {
- var low, high, mid, midVal, lindex, rindex;
- low = 0;
- high = bucket_.length-1;
- while (low <= high) {
- mid = Math.floor((low + high) / 2);
- midVal = bucket_[mid];
- if (midVal < lkey_) {
- low = mid + 1;
- } else if (midVal > lkey_) {
- high = mid - 1;
- } else { // midval == lkey_
- low = mid;
- break;
- }
- }
- lindex = low;
- high = bucket_.length-1;
- while (low <= high) {
- mid = Math.floor((low + high) / 2);
- midVal = bucket_[mid];
- if (midVal < rkey_) {
- low = mid + 1;
- } else if (midVal > rkey_) {
- high = mid - 1;
- } else { // midval == rkey_
- high = mid;
- break;
- }
- }
- rindex = high + 1;
- return bucket_.slice(lindex, rindex);
- };
- ;;; $c._range = _range;
- // ---------- Linked List ----------
- var LinkedList = function() {
- this.first = null;
- this.last = null;
- this.length = 0;
- };
- ;;; $c.LinkedList = LinkedList;
- var LinkedListNode = function(data) {
- this.prev = null;
- this.next = null;
- this.data = data;
- };
- ;;; $c.LinkedListNode = LinkedListNode;
- LinkedList.prototype.prepend = function(node) {
- if (this.first === null) {
- this.first = node;
- this.last = node;
- } else {
- node.prev = null;
- node.next = this.first;
- this.first.prev = node;
- this.first = node;
- }
- this.length++;
- };
- LinkedList.prototype.append = function(node) {
- if (this.first === null) {
- this.first = node;
- this.last = node;
- } else {
- node.prev = this.last;
- node.next = null;
- this.last.next = node;
- this.last = node;
- }
- this.length++;
- };
- LinkedList.prototype.remove = function(node) {
- if (this.first == this.last) {
- this.first = null;
- this.last = null;
- } else {
- if (node.prev) {
- node.prev.next = node.next;
- }
- if (node.next) {
- node.next.prev = node.prev;
- }
- if (node == this.first) {
- this.first = node.next;
- }
- if (node == this.last) {
- this.last = node.prev;
- }
- }
- this.length--;
- node.prev = null;
- node.next = null;
- };
- // ---------- Mapped Linked List ----------
- var MappedLinkedList = function() {
- this.map = {};
- this.list = new LinkedList();
- };
- ;;; $c.MappedLinkedList = MappedLinkedList;
- MappedLinkedList.prototype.add = function(key, val) {
- if (typeof this.map[key] !== "undefined") {
- return; // don't re-add
- }
- var node = new LinkedListNode(val);
- this.map[key] = node;
- this.list.append(node);
- };
- MappedLinkedList.prototype.remove = function(key) {
- if (typeof this.map[key] === "undefined") {
- return;
- }
- this.list.remove(this.map[key]);
- delete(this.map[key]);
- };
- // ---------- Pub/Sub Broker ----------
- var PubSubBroker = function() {
- this.channels = {};
- };
- PubSubBroker.prototype = {
- uid_counter: 0
- };
- ;;; $c.PubSubBroker = PubSubBroker;
- PubSubBroker.prototype.publish = function(signal) {
- setTimeout((function(signal) { // make it asynch
- var passed = arguments;
- var channels = this.channels;
- return function() {
- var args = Array.prototype.slice.call(passed, 1);
- var node = channels[signal] && channels[signal].list.first;
- while (node) {
- var handler = node.data;
- setTimeout((function(handler, args) { // make it asynch
- return function() {
- handler.apply(this, args);
- };
- }).call(this, handler, args), 0);
-
- node = node.next;
- }
- };
- }).apply(this, arguments), 0);
- };
- PubSubBroker.prototype.subscribe = function(signal, scope, handlerName){
- if (typeof this.channels[signal] === "undefined") {
- this.channels[signal] = new MappedLinkedList();
- }
- var curryArray = Array.prototype.slice.call(arguments, 3);
- if (typeof scope._psb_uid == "undefined") {
- scope._psb_uid = ++this.uid_counter;
- }
- var key = scope._psb_uid + ":" + handlerName + ":" + curryArray;
- this.channels[signal].add(key, function(){
- var normalizedArgs = Array.prototype.slice.call(arguments, 0);
- scope[handlerName].apply((scope || window), curryArray.concat(normalizedArgs));
- });
- };
- PubSubBroker.prototype.unsubscribe = function(signal, scope, handlerName) {
- if (typeof this.channels[signal] === "undefined") {
- return; // throw warn?
- }
- var curryArray = Array.prototype.slice.call(arguments, 3);
- var key = scope._psb_uid + ":" + handlerName + ":" + curryArray;
- this.channels[signal].remove(key);
- };
- // ========== Canvas Utilities ==========
- // ---------- Context Helpers ----------
- function decorate_context(context_) {
- context_._calc = function(pos_) {
- return context_.lineWidth % 2 ? Math.floor(pos_) + 0.5 : Math.floor(pos_);
- };
- context_._moveTo = function(x_, y_) {
- context_.moveTo(context_._calc(x_), context_._calc(y_));
- };
- context_._lineTo = function(x_, y_) {
- context_.lineTo(context_._calc(x_), context_._calc(y_));
- };
- }
- // ========== Chart ==========
- // ---------- LineDataCache ----------
- //TODO: consider: auto-generating the zoom schema
- var _ZOOM_SCHEMA_HALF = [
- // [delta, low period, high period]
- [60, 1, 15],
- [60*5, 3, 12],
- [60*30, 2, 12],
- [60*60*3, 2, 16],
- [60*60*12, 4, 16]
- ];
- var _ZOOM_SCHEMA_RELAXED = [
- // [delta, low period, high period]
- [60, 1, 20],
- [60*5, 4, 18],
- [60*30, 3, 18],
- [60*60*3, 3, 20],
- [60*60*12, 5, 31]
- ];
- var _ZOOM_SCHEMA_STANDARD = [
- // [delta, low period, high period]
- [60, 1, 30],
- [60*5, 6, 30],
- [60*30, 5, 30],
- [60*60*3, 5, 32],
- [60*60*12, 8, 31]
- ];
- var _ZOOM_SCHEMA_AGGRESSIVE = [
- // [delta, low period, high period]
- [60, 1, 40],
- [60*5, 8, 42],
- [60*30, 7, 42],
- [60*60*3, 7, 40],
- [60*60*12, 10, 31]
- ];
- var _ZOOM_SCHEMA_INSANE = [
- // [delta, low period, high period]
- [60, 1, 60],
- [60*5, 12, 60],
- [60*30, 10, 60],
- [60*60*3, 10, 60],
- [60*60*12, 15, 31]
- ];
- var _ZOOM_SCHEMA_MAX = [
- // [delta, low period, high period]
- [60, 1, 120],
- [60*5, 24, 120],
- [60*30, 20, 120],
- [60*60*3, 20, 120],
- [60*60*12, 30, 31]
- ];
- /**
- * Data Cache for a Line
- * a cache for each zoom scale [different granularity of time data]
- * w/in each zoom scale cache, the point interval is the time delta at that zoom scale
- * @class
- */
- var LineDataCache = function(model_) {
- // Initialize
- var
- model = model_,
- min_time = 0,
- max_time = 0,
- bucket_size = 0,
- min_buckets = 0,
- max_buckets = 0,
- bucket_ttl = 0,
- cache = [], // at zoom scale
- proxy_bucket = [];
- this.model = function() { return model; };
- this.min_time = function() { return min_time; };
- this.max_time = function() { return max_time; };
- this.cache = function() { return cache; };
- /* Broker Channels:
- /cache/flush flush expired buckets
- */
- // Setup
- function init(min_time_, max_time_) {
- min_time = min_time_;
- max_time = max_time_;
- bucket_size = LineDataCache.prototype.bucket_size;
- min_buckets = LineDataCache.prototype.min_buckets;
- max_buckets = LineDataCache.prototype.max_buckets;
- bucket_ttl = LineDataCache.prototype.bucket_ttl;
- proxy_bucket.length = bucket_size;
- for (var i=0; i<model.zoom_schema().length; i++) {
- cache[i] = {};
- }
- //FIXME: pull these for real
- //XXX: bucket: 0; increment: 60
- for (var i=0; i<4; i++) {
- var bucket = [];
- bucket.length = bucket_size; //XXX: set length to get undefined from slice
- var range = 70-i*10;
- for (var j=0; j<bucket_size; j++) {
- if (i==2) {
- bucket[j] = 50;
- } else {
- bucket[j] = range+Math.random()*30;
- }
- }
- cache[0][i] = bucket;
- }
- var zoom_schema = model.zoom_schema();
- if (zoom_schema.length >= 2) {
- //XXX: bucket: 1; increment: 60*5
- __sum = function(x,y) { return (x || 0) + (y || 0); };
- _sum = function(list_) { return _reduce(__sum, list_, 0); };
- cache[1][0] = [];
- cache[1][0].length = bucket_size;
- var k=0;
- for (i=0; i<4; i++) {
- for (j=0; j<bucket_size; j+=5) {
- cache[1][0][k++] = _sum(cache[0][i].slice(j,j+5)) / 5;
- }
- }
- var len = k;
- }
- if (zoom_schema.length >= 3) {
- //XXX: bucket: 2; increment: 60*30
- cache[2][0] = [];
- cache[2][0].length = bucket_size;
- k=0;
- for (i=0; i<len; i+=6) {
- cache[2][0][k++] = _sum(cache[1][0].slice(i,i+6)) / 6;
- }
- len = k;
- }
- if (zoom_schema.length >= 4) {
- //XXX: bucket: 3; increment: 60*60*3
- cache[3][0] = [];
- cache[3][0].length = bucket_size;
- k=0;
- for (i=0; i<len; i+=6) {
- cache[3][0][k++] = _sum(cache[2][0].slice(i,i+6)) / 6;
- }
- len = k;
- }
- if (zoom_schema.length >= 5) {
- //XXX: bucket: 4; increment: 60*60*12
- cache[4][0] = [];
- cache[4][0].length = bucket_size;
- k=0;
- for (i=0; i<len; i+=4) {
- cache[4][0][k++] = _sum(cache[3][0].slice(i,i+4)) / 4;
- }
- }
- // Register for Upkeep
- var broker = model.broker();
- broker.subscribe("/cache/flush", this, "_flush");
- }
- this.init = init;
- // Data
- /**
- * @function
- * @param {int} zoom_scale_ cache data level to use
- * @param {int} start_offset_ first point in range
- * @param {int} end_offset_ last point in range
- * @returns cache[zoom_scale_][start_offset_:end_offset_+1]
- */
- function fetch(zoom_scale_, start_offset_, end_offset_) {
- var _data = cache[zoom_scale_];
- var start_idx = Math.floor(start_offset_/bucket_size);
- var end_idx = Math.floor(end_offset_/bucket_size);
- //XXX: avoid % (IE6 is slow)
- start_offset_ -= start_idx * bucket_size;
- end_offset_ -= end_idx * bucket_size;
- end_offset_ += 1; //XXX: compensate for exclusive
- if (start_idx === end_idx) {
- var _bucket = _data[start_idx];
- if (!_bucket) {
- _bucket = proxy_bucket;
- //TODO: fetch bucket from remote data source
- }
- return _bucket.slice(start_offset_, end_offset_);
- }
- var _start_bucket = _data[start_idx];
- if (!_start_bucket) {
- _start_bucket = proxy_bucket;
- //TODO: fetch bucket from remote data source
- }
- var _end_bucket = _data[end_idx];
- if (!_end_bucket) {
- _end_bucket = proxy_bucket;
- //TODO: fetch bucket from remote data source
- }
- var start_range = _start_bucket.slice(start_offset_, bucket_size);
- var end_range = _end_bucket.slice(0, end_offset_);
- return start_range.concat(end_range);
- }
- this.fetch = fetch;
-
- // Upkeep
- function _flush() {
- //TODO: flush expired buckets
- }
- this._flush = _flush;
- };
- //TODO: implement bucket caching
- //TODO: implement cache using a MappedLinkedList
- LineDataCache.prototype = {
- bucket_size: 60*24, // # of time points (depending on zoom scale)
- min_buckets: 10, // # of buckets to keep, regardless of TTL
- max_buckets: 100, // # of buckets to keep, at any time
- bucket_ttl: 60*1000 // 1m
- };
- // ---------- Model ----------
- var Model = function(parent_, zoom_schema_) {
- // Initialize
- var
- parent = parent_,
- zoom_schema = zoom_schema_,
- broker = parent.broker(),
- min_time = 0,
- max_time = 0,
- _time_range = 0,
- data = [],
- upkeep_interval = Model.prototype.upkeep_interval;
- this.parent = function() { return parent; };
- this.zoom_schema = function() { return zoom_schema; };
- this.zoom_level = function() { return zoom_schema[parent.zoom_scale()]; };
- this.zoom_delta = function() { return zoom_schema[parent.zoom_scale()][0]; };
- this.broker = function() { return broker; };
- this.min_time = function() { return min_time; };
- this.max_time = function() { return max_time; };
- this._time_range = function() { return _time_range; };
- this.data = function() { return data; };
- // Setup
- function init(min_time_, max_time_, chart_width_, pane_width_) {
- min_time = min_time_;
- max_time = max_time_;
-
- _time_range = max_time - min_time;
- // calculate: max zoom level and period
- var _num_panes = chart_width_ / pane_width_;
- for (var i=0; i<zoom_schema.length; i++) {
- var _level = zoom_schema[i];
- if (_time_range < (_level[0]*chart_width_)) {
- zoom_schema.length = i+1;
- _max_points = Math.floor(_time_range / _level[0]);
- zoom_schema[i][2] = Math.ceil(_max_points / _num_panes);
- break;
- }
- }
- //FIXME: create this for real
- var line_cache = new LineDataCache(this);
- line_cache.init(min_time, max_time);
- data[0] = line_cache;
- // Upkeep Thread
- setTimeout(_upkeep, upkeep_interval);
- }
- this.init = init;
- // Data
- function fetch(zoom_scale_, start_offset_, period_) {
- var _end_offset = start_offset_ + period_;
- var section = [];
- for (var i=data.length-1; i>=0; i--) {
- section.push(data[i].fetch(zoom_scale_, start_offset_, _end_offset));
- }
- section.reverse(); //XXX: think: is this necessary? consider how to identify lines
- return section;
- }
- this.fetch = fetch;
- // Upkeep
- function _upkeep() {
- broker.publish("/cache/flush");
- setTimeout(_upkeep, upkeep_interval);
- }
- };
- Model.prototype = {
- //TODO: make zoom_schema private (potentially, multiple charts)
- upkeep_interval: 60*1000 // background upkeep thread interval
- };
- // ---------- Pane ----------
- var PLOT_LINE_WIDTH = 1.25;
- if ($.browser.msie) { //XXX: hack: IE6
- PLOT_LINE_WIDTH = 1.6;
- }
- var BOTTOM_TAPE_HEIGHT = 16;
- var BOTTOM_TAPE_TOP_ADJUST = 0;
- //var BOTTOM_TAPE_COLOR = "rgb(147,190,255)";
- //var BOTTOM_TAPE_COLOR = "rgb(198,228,255)";
- var BOTTOM_TAPE_COLOR = "rgb(194,218,241)";
- if ($.browser.msie) { //XXX: hack: IE6
- BOTTOM_TAPE_TOP_ADJUST = -1;
- }
- var TAPE_MARKER_HEIGHT = 5;
- var Pane = function(parent_, width_) {
- // Initialize
- var
- parent = parent_,
- model = parent.model(),
- time_offset = 0,
- _data_offset = 0,
- width = width_,
- _half_width = Math.floor(width/2),
- draw_height = 0,
- chart_height = 0,
- container = null,
- _style = null,
- canvas = null,
- c = null,
- tape = null,
- _data = [],
- _min_y = 0,
- _max_y = 0;
-
- this.parent = function() { return parent; };
- this.time_offset = function() { return time_offset; };
- this._data_offset = function() { return _data_offset; };
- this.container = function() { return container; };
- this.canvas = function() { return canvas; };
- this.offset = function() { return parseInt(_style.left, 10); };
- this.c = function() { return c; };
- this.tape = function() { return tape; };
- this._data = function() { return _data; };
- this._min_y = function() { return _min_y; };
- this._max_y = function() { return _max_y; };
- /* Broker Channels:
- /pane/translate translate pane by delta
- /pane/redraw redraw pane
- */
- // Setup
- function init() {
- // Setup Variables
- draw_height = parent.draw_height();
- chart_height = parent.chart_height();
- // Container
- container = $([
- '<div',
- 'style="',
- 'position: absolute;',
- 'left: 0px;',
- 'top: 0px;',
- 'z-index: 0;',
- 'width: '+width+'px;',
- 'height: '+draw_height+'px;',
- '"></div>'].join(' ')).appendTo(parent.container());
- _style = container[0].style;
- // Canvas
- //XXX: build manually (excanvas r3 workaround)
- canvas = document.createElement("canvas");
- canvas.width = width;
- canvas.height = chart_height;
- var _canvas = $(canvas);
- _canvas.css("position", "absolute");
- _canvas.css("left", "0px");
- _canvas.css("top", "0px");
- _canvas.css("z-index", "0");
- _canvas.css("width", width+"px");
- _canvas.css("height", chart_height+"px");
- _canvas.appendTo(container);
- if ($.browser.msie) { //XXX: hack: excanvas
- canvas = window.G_vmlCanvasManager.initElement(canvas);
- }
- c = canvas.getContext("2d");
- decorate_context(c);
- if ($.browser.msie && c.setTransform) {
- //XXX: exception: IE6 renders more smoothly w/ setTransform
- c.setTransform(1, 0, 0, -1, 0, chart_height);
- }
- // Tape
- tape = $([
- '<div',
- 'style="',
- 'position: absolute;',
- 'left: 0px;',
- 'top: '+chart_height+'px;',
- 'z-index: 0;',
- 'width: '+width+'px;',
- 'height: '+(draw_height-chart_height)+'px;',
- 'text-align: left;',
- '"></div>'].join(' ')).appendTo(container);
- // Broker Subscriptions
- var broker = parent.broker();
- broker.subscribe("/pane/translate", this, "translate");
- broker.subscribe("/pane/redraw", this, "_draw");
-
- //XXX: needs reset()
- }
- this.init = init;
- // Translate Methods
- function translate(delta_) {
- _style.left = (parseInt(_style.left, 10)+delta_)+"px";
- }
- this.translate = translate;
- // Draw Methods
- function __grid_x(x_tick_) {
- if ($.browser.msie && c.setTransform) {
- //XXX: exception: IE6 renders more smoothly w/ setTransform
- __grid_x = function(x_tick_) {
- c.lineWidth = 1;
- // Vertical Line at 0
- c.beginPath();
- c.strokeStyle = "rgb(200,200,200)";
- c._moveTo(x_tick_, 0);
- c._lineTo(x_tick_, chart_height);
- c.stroke();
- c.beginPath();
- c.strokeStyle = "rgb(100,100,100)";
- c._moveTo(x_tick_, 0);
- c._lineTo(x_tick_, TAPE_MARKER_HEIGHT);
- c.stroke();
- };
- } else { //XXX: Safari,FF2 don't support setTransform; FF3 renders faster
- __grid_x = function(x_tick_) {
- c.lineWidth = 1;
- // Vertical Line at 0
- c.beginPath();
- c.strokeStyle = "rgb(200,200,200)";
- c._moveTo(x_tick_, 0);
- c._lineTo(x_tick_, chart_height);
- c.stroke();
- c.beginPath();
- c.strokeStyle = "rgb(100,100,100)";
- c._moveTo(x_tick_, chart_height);
- var _tape_y = chart_height-TAPE_MARKER_HEIGHT;
- c._lineTo(x_tick_, _tape_y);
- c.stroke();
- };
- }
- return __grid_x(x_tick_);
- }
- this.__grid_x = __grid_x;
- function __grid_y(y_intervals_) {
- if ($.browser.msie && c.setTransform) {
- //XXX: exception: IE6 renders more smoothly w/ setTransform
- __grid_y = function(y_intervals_) {
- c.lineWidth = 1;
- // Horizontal Lines
- c.strokeStyle = "rgb(200,200,200)";
- //XXX: consider Duff's device
- for (var i=y_intervals_.length-1; i>=0; i--) {
- c.beginPath();
- var y = y_intervals_[i];
- c._moveTo(0, y);
- c._lineTo(width, y);
- c.stroke();
- }
- };
- } else { //XXX: Safari,FF2 don't support setTransform; FF3 renders faster
- __grid_y = function(y_intervals_) {
- c.lineWidth = 1;
- // Horizontal Lines
- c.strokeStyle = "rgb(200,200,200)";
- //XXX: consider Duff's device
- for (var i=y_intervals_.length-1; i>=0; i--) {
- c.beginPath();
- var _y = chart_height - y_intervals_[i];
- c._moveTo(0, _y);
- c._lineTo(width, _y);
- c.stroke();
- }
- };
- }
- return __grid_y(y_intervals_);
- }
- this.__grid_y = __grid_y;
- function __plot(x_scale_, y_scale_, points_) {
- if ($.browser.msie && c.setTransform) {
- //XXX: exception: IE6 renders more smoothly w/ setTransform
- __plot = function(x_scale_, y_scale_, points_) {
- c.lineWidth = PLOT_LINE_WIDTH;
- c.strokeStyle = "rgb(25,106,227)";
- c.lineJoin = "miter"; //XXX: don't use round (prob w/ seams)
- c.beginPath();
- // first point @ 0
- //XXX: fix seams: account for idx: -1
- c._moveTo(-1*x_scale_, (points_[-1] || 0)*y_scale_);
- c._lineTo(0, (points_[0] || 0)*y_scale_);
- //XXX: faster cleaner modified duff's device
- //XXX: ref: http://home.earthlink.net/~kendrasg/info/js_opt/jsOptMain.html
- var i = 1;
- var iter = points_.length-2;
- var n = iter % 8;
- while (n--) {
- c._lineTo(i*x_scale_, (points_[i] || 0)*y_scale_); i++;
- }
- n = parseInt(iter/8, 10);
- while (n--) {
- c._lineTo(i*x_scale_, (points_[i] || 0)*y_scale_); i++;
- c._lineTo(i*x_scale_, (points_[i] || 0)*y_scale_); i++;
- c._lineTo(i*x_scale_, (points_[i] || 0)*y_scale_); i++;
- c._lineTo(i*x_scale_, (points_[i] || 0)*y_scale_); i++;
- c._lineTo(i*x_scale_, (points_[i] || 0)*y_scale_); i++;
- c._lineTo(i*x_scale_, (points_[i] || 0)*y_scale_); i++;
- c._lineTo(i*x_scale_, (points_[i] || 0)*y_scale_); i++;
- c._lineTo(i*x_scale_, (points_[i] || 0)*y_scale_); i++;
- }
- // last point @ width
- c._lineTo(width, (points_[points_.length-1] || 0)*y_scale_);
- c._lineTo(width+1, (points_[points_.length-1] || 0)*y_scale_);
- c.stroke();
- c._lineTo(width+1, 0);
- //XXX: fix seams: account for idx: -1
- c._lineTo(-1*x_scale_, 0);
- c.fillStyle = "rgba(183,220,255, 0.4)";
- c.fill();
- };
- } else { //XXX: Safari,FF2 don't support setTransform; FF3 renders faster
- __plot = function(x_scale_, y_scale_, points_) {
- c.lineWidth = PLOT_LINE_WIDTH;
- c.strokeStyle = "rgb(25,106,227)";
- c.lineJoin = "miter"; //XXX: don't use round (prob w/ seams)
- c.beginPath();
- // first point @ 0
- //XXX: fix seams: account for idx: -1
- c._moveTo(-1*x_scale_, chart_height - (points_[-1] || 0)*y_scale_);
- c._lineTo(0, chart_height - (points_[0] || 0)*y_scale_);
- //XXX: faster cleaner modified duff's device
- //XXX: ref: http://home.earthlink.net/~kendrasg/info/js_opt/jsOptMain.html
- var i = 1;
- var iter = points_.length-2;
- var n = iter % 8;
- while (n--) {
- c._lineTo(i*x_scale_, chart_height - (points_[i] || 0)*y_scale_); i++;
- }
- n = parseInt(iter/8, 10);
- while (n--) {
- c._lineTo(i*x_scale_, chart_height - (points_[i] || 0)*y_scale_); i++;
- c._lineTo(i*x_scale_, chart_height - (points_[i] || 0)*y_scale_); i++;
- c._lineTo(i*x_scale_, chart_height - (points_[i] || 0)*y_scale_); i++;
- c._lineTo(i*x_scale_, chart_height - (points_[i] || 0)*y_scale_); i++;
- c._lineTo(i*x_scale_, chart_height - (points_[i] || 0)*y_scale_); i++;
- c._lineTo(i*x_scale_, chart_height - (points_[i] || 0)*y_scale_); i++;
- c._lineTo(i*x_scale_, chart_height - (points_[i] || 0)*y_scale_); i++;
- c._lineTo(i*x_scale_, chart_height - (points_[i] || 0)*y_scale_); i++;
- }
- // last point @ width
- c._lineTo(width, chart_height - (points_[points_.length-1] || 0)*y_scale_);
- c._lineTo(width+1, chart_height - (points_[points_.length-1] || 0)*y_scale_);
- c.stroke();
- c.fillStyle = "rgba(183,220,255, 0.4)";
- c._lineTo(width+1, chart_height);
- //XXX: fix seams: account for idx: -1
- c._lineTo(-1*x_scale_, chart_height);
- c.fill();
- };
- }
- return __plot(x_scale_, y_scale_, points_);
- }
- this.__plot = __plot;
- function __tape_marker(x_tick_, x_text_) {
- //TODO: consider: color code tape?
- // Set Text
- tape.append([
- '<div',
- 'class="tape-tick"',
- 'style="',
- 'position: absolute;',
- 'left: '+x_tick_+'px;',
- 'top: 0px;',
- 'z-index: 0;',
- 'width: '+width+'px;',
- '">'+x_text_+'</div>'].join(' '));
- }
- this.__tape_marker = __tape_marker;
- function _fetch_data() {
- //XXX: fix seams: grab idx: -1
- _data = model.fetch(
- parent.zoom_scale(),
- _data_offset-1,
- parent._pane_period()+1);
-
- // save min/max-y
- if (_data.length === 0) {
- _min_y = 0;
- _max_y = 0;
- return;
- }
- var i,j, _source;
- //XXX: fix seams: put idx: -1 at [-1]
- for (i=_data.length-1; i>=0; i--) {
- _source = _data[i];
- _source[-1] = _source.shift();
- }
- _min_y = _max_y = _data[0][0] || 0;
- for (i=_data.length-1; i>=0; i--) {
- _source = _data[i];
- for (j=_source.length-1; j>=0; j--) {
- var _y = _source[j] || 0;
- _min_y = Math.min(_min_y, _y);
- _max_y = Math.max(_max_y, _y);
- }
- }
- }
- function _draw() {
- // reset canvas
- c.clearRect(0, 0, width, draw_height);
- // reset tape
- tape.empty();
- // determine: scale
- var x_scale = parent._x_scale();
- var y_scale = parent._y_scale();
- // draw grid
- var _x_data = parent._x_tick()(time_offset);
- var _x_tick = (_x_data[0] / model.zoom_delta()) * x_scale;
- if (_x_tick < width) {
- __grid_x(_x_tick);
- // draw tape
- __tape_marker(_x_tick, _x_data[1]);
- }
- __grid_y(parent._y_intervals());
- // draw chart
- for (var i=_data.length-1;i>=0;i--) {
- __plot(x_scale, y_scale, _data[i]);
- }
- }
- this._draw = _draw;
- function _reset(data_offset_, offset_) {
- _data_offset = data_offset_;
- _style.left = offset_ + "px";
-
- time_offset = model.min_time() + _data_offset * model.zoom_delta();
- _fetch_data();
- }
- this._reset = _reset;
- function reset(data_offset_, offset_) {
- _reset(data_offset_, offset_);
- _draw();
- }
- this.reset = reset;
- function range_min_max_y(start_, end_) {
- if (_data.length === 0) {
- return [0, 0];
- }
- var min, max;
- min = max = _data[0][start_] || 0;
- for (var i=_data.length-1; i>=0; i--) {
- var _source = _data[i].slice(start_, end_);
- for (var j=_source.length-1; j>=0; j--) {
- var _y = _source[j] || 0;
- min = Math.min(min, _y);
- max = Math.max(max, _y);
- }
- }
- return [min, max];
- }
- this.range_min_max_y = range_min_max_y;
- };
- // ---------- Navigation Pane ----------
- NAV_HEIGHT = 75;
- NAV_BUTTON_HEIGHT = 13;
- NAV_BUTTON_WIDTH = 16;
- NAV_SCROLL_RATE = 1000 * 0.1;
- NAV_SCROLL_SPEED = 50;
- NAV_SCROLL_MIN_WIDTH = 20;
- NAV_SCROLL_MID_THRESHOLD = 60;
- NAV_OVERVIEW_TOP_OFFSET = 5;
- NAV_OVERVIEW_SCALE_FACTOR = 1.1;
- NAV_RANGE_OFFSET = -9;
- NAV_RANGE_WIDTH = 13;
- NAV_RANGE_CENTER_THRESHOLD = 13;
- NAV_RANGE_CENTER_OFFSET = -8;
- var NavigationPane = function(parent_) {
- // Initialize
- var
- parent = parent_,
- model = parent.model(),
- broker = parent.broker(),
- width = 0,
- height = 0,
- container = null,
- nav_left = null,
- nav_right = null,
- nav_bar = null,
- zoom_level = null,
- overview_left = 0,
- overview = null,
- oc = null,
- preview = null,
- _preview = null,
- pc = null,
- _data = [],
- _x_scale = 0,
- _y_scale = 0,
- viewer = null,
- _viewer = null,
- viewer_width = 0,
- slider = null,
- _slider = null,
- slider_middle = null,
- drag_modifier = 1,
- range_min = null,
- _range_min = null,
- range_max = null,
- _range_max = null,
- __is_hover__ = false,
- __is_drag_min__ = false;
-
- this.parent = function() { return parent; };
- this.model = function() { return model; };
- this.width = function() { return width; };
- this.height = function() { return height; };
- this.container = function() { return container; };
- this.zoom_level = function() { return zoom_level; };
- this.overview_left = function() { return overview_left; };
- this.overview = function() { return overview; };
- this.oc = function() { return oc; };
- this.preview = function() { return preview; };
- this._preview = function() { return _preview; };
- this.pc = function() { return pc; };
- this._data = function() { return _data; };
- this._x_scale = function() { return _x_scale; };
- this._y_scale = function() { return _y_scale; };
- this.viewer = function() { return viewer; };
- this._viewer = function() { return _viewer; };
- this.viewer_width = function() { return viewer_width; };
- this.slider = function() { return slider; };
- this._slider = function() { return _slider; };
- this.slider_middle = function() { return slider_middle; };
- this.drag_modifier = function() { return drag_modifier; };
- this.range_min = function() { return range_min; };
- this._range_min = function() { return _range_min; };
- this.range_max = function() { return range_max; };
- this._range_max = function() { return _range_max; };
- /* Broker Channels:
- /nav/center (re-)center nav
- /nav/translate translate nav
- */
- // Helper Methods
- function _render_overview(context_, strokeStyle_, fillStyle_, boxStrokeStyle_) {
- // reset canvas
- context_.clearRect(0, 0, overview.width, overview.height);
- context_.lineWidth = 1;
- context_.strokeStyle = strokeStyle_;
- context_.lineJoin = "round";
- context_.beginPath();
- // last point @ width
- context_._moveTo(overview.width, overview.height - (_data[_data.length-2] || 0) * _y_scale);
-
- // draw overview
- for (var i=_data.length-3; i>0; i--) {
- context_._lineTo(i*_x_scale, overview.height - (_data[i] || 0) * _y_scale);
- }
- // first point @ 0
- context_._lineTo(
- 0,
- overview.height - (_data[0] || 0) * _y_scale);
-
- context_.stroke();
- // fill overview
- context_.fillStyle = fillStyle_;
- context_._lineTo(0, overview.height);
- context_._lineTo(overview.width, overview.height);
- context_.fill();
- // draw outline
- context_.lineWidth = 1;
- context_.strokeStyle = boxStrokeStyle_;
- context_.lineJoin = "round";
- context_.beginPath();
- context_.moveTo(0, overview.height);
- context_.lineTo(0, 0);
- context_.lineTo(overview.width, 0);
- context_.lineTo(overview.width, overview.height);
- context_.stroke();
- }
- // Setup
- function init(target_, width_, height_, top_, left_) {
- // Setup Variables
- width = width_;
- height = height_;
- // Container
- container = $([
- '<div',
- 'style="',
- 'position: absolute;',
- 'left: '+left_+'px;',
- 'top: '+top_+'px;',
- 'width: '+width+'px;',
- 'height: '+height+'px;',
- '"></div>'].join(' ')).appendTo(target_);
-
- // Navigation Scroll Bar
- nav_left = $([
- '<div',
- 'class="nav-left-unsel"',
- 'style="',
- 'position: absolute;',
- 'overflow: hidden;',
- 'left: '+(-BORDER_OFFSET_X)+'px;',
- 'top: '+(NAV_HEIGHT-NAV_BUTTON_HEIGHT)+'px;',
- 'width: '+NAV_BUTTON_WIDTH+'px;',
- 'height: '+NAV_BUTTON_HEIGHT+'px;',
- '"></div>'].join(' ')).appendTo(container);
- nav_right = $([
- '<div',
- 'class="nav-unsel nav-right-unsel"',
- 'style="',
- 'position: absolute;',
- 'overflow: hidden;',
- 'left: '+(-BORDER_OFFSET_X+NAV_BUTTON_WIDTH)+'px;',
- 'top: '+(NAV_HEIGHT-NAV_BUTTON_HEIGHT)+'px;',
- 'width: '+NAV_BUTTON_WIDTH+'px;',
- 'height: '+NAV_BUTTON_HEIGHT+'px;',
- '"></div>'].join(' ')).appendTo(container);
- nav_bar = $([
- '<div',
- 'style="',
- 'position: absolute;',
- 'left: '+(BORDER_OFFSET_X+2*NAV_BUTTON_WIDTH)+'px;',
- 'top: '+(NAV_HEIGHT-NAV_BUTTON_HEIGHT)+'px;',
- 'width: '+(width-BORDER_OFFSET_X-2*NAV_BUTTON_WIDTH)+'px;',
- 'height: '+(NAV_BUTTON_HEIGHT)+'px;',
- 'font-size: 0px;', // hack: IE6 fix
- 'border-top: 1px solid #aaa;',
- 'border-right: 1px solid #aaa;',
- 'border-bottom: 1px solid #aaa;',
- '"></div>'].join(' ')).appendTo(container);
- // Overview
- //XXX: build manually (excanvas r3 workaround)
- overview = document.createElement("canvas");
- overview.width = width-2*NAV_BUTTON_WIDTH;
- overview.height = NAV_HEIGHT-NAV_BUTTON_HEIGHT-NAV_OVERVIEW_TOP_OFFSET;
-
- overview_left = BORDER_OFFSET_X+2*NAV_BUTTON_WIDTH;
- var _overview = $(overview);
- _overview.css("position", "absolute");
- _overview.css("left", overview_left+"px");
- _overview.css("top", NAV_OVERVIEW_TOP_OFFSET+"px");
- _overview.css("z-index", "0");
- _overview.css("width", overview.width+"px");
- _overview.css("height", overview.height+"px");
- _overview.appendTo(container);
- if ($.browser.msie) { //XXX: hack: excanvas
- overview = window.G_vmlCanvasManager.initElement(overview);
- }
- oc = overview.getContext("2d");
- decorate_context(oc);
- // Draw Overview
- var _zoom_idx = model.zoom_schema().length - 1;
- zoom_level = model.zoom_schema()[_zoom_idx];
- _data = model.fetch(
- _zoom_idx,
- 0,
- Math.ceil(model._time_range() / zoom_level[0]));
- _data = _data[0] || []; //XXX: use first data source for overview
- // Calculate X-coord Scale
- _x_scale = overview.width / (_data.length - 1);
- // Calculate Y-coord Scale
- //XXX: optional: make it logarithmic?
- //_data = _map(function (x) { return Math.log(x || 1); }, _data);
- var _min_y, _max_y, _point;
- _min_y = _max_y = _data[_data.length-1] || 0;
- for (var i=_data.length-2; i>=0; i--) {
- _point = _data[i] || 0;
- _min_y = _point < _min_y ? _point : _min_y;
- _max_y = _point > _max_y ? _point : _max_y;
- }
- _max_y = (_max_y - _min_y) * NAV_OVERVIEW_SCALE_FACTOR + _min_y;
-
- _y_scale = (overview.height - 1) / (_max_y - _min_y);
- _render_overview(oc, "rgb(225,225,225)", "rgba(200,200,200,0.2)", "rgb(200,200,200)");
- // Slider
- slider = $([
- '<div',
- 'class="nav-slider"',
- 'style="',
- 'display: none;',
- 'position: absolute;',
- 'overflow: hidden;',
- 'left: '+overview_left+'px;',
- 'top: '+(NAV_HEIGHT-NAV_BUTTON_HEIGHT)+'px;',
- 'width: 10px;',
- 'height: '+NAV_BUTTON_HEIGHT+'px;',
- 'text-align: center;',
- '"></div>'].join(' ')).appendTo(container);
- _slider = slider[0];
- slider.append([
- '<img',
- 'src="/assets/slider.left.gif"',
- 'style="',
- 'position: absolute;',
- 'top: 0px;',
- 'left: 0px;',
- '"/>'].join(' '));
- slider.append([
- '<img',
- 'src="/assets/slider.right.gif"',
- 'style="',
- 'position: absolute;',
- 'top: 0px;',
- 'right: 0px;',
- '"/>'].join(' '));
- slider_middle = $([
- '<img',
- 'src="/assets/slider.middle.gif"',
- 'style="',
- 'display: none;',
- '"/>'].join(' ')).appendTo(slider);
- // Viewer
- viewer = $([
- '<div',
- 'class="nav-viewer"',
- 'style="',
- 'position: absolute;',
- 'left: '+overview_left+'px;',
- 'top: 0px;',
- 'width: 1px;',
- 'height: '+(NAV_HEIGHT-NAV_BUTTON_HEIGHT)+'px;',
- '"></div>'].join(' ')).appendTo(container);
- _viewer = viewer[0];
- //XXX: hide preview, but allow range controls to overflow
- var internal = $([
- '<div',
- 'style="',
- 'position: absolute;',
- 'overflow: hidden;',
- 'left: 0px;',
- 'top: 0px;',
- 'width: 100%;',
- 'height: 100%;',
- '"><div>'].join(' ')).appendTo(viewer);
- // Preview
- //XXX: build manually (excanvas r3 workaround)
- preview = document.createElement("canvas");
- preview.width = overview.width;
- preview.height = overview.height;
- _preview = $(preview);
- _preview.css("position", "absolute");
- _preview.css("left", (-BORDER_OFFSET_X)+"px");
- _preview.css("top", NAV_OVERVIEW_TOP_OFFSET+"px");
- _preview.css("width", preview.width+"px");
- _preview.css("height", preview.height+"px");
- _preview.css("z-index", "0");
- _preview.css("border-bottom", "1px solid #aaa");
- _preview.appendTo(internal);
- if ($.browser.msie) { //XXX: hack: excanvas
- preview = window.G_vmlCanvasManager.initElement(preview);
- }
- pc = preview.getContext("2d");
- decorate_context(pc);
- _render_overview(pc, "rgb(25,106,227)", "rgba(25,106,227,0.1)", "rgb(200,200,200)");
- //XXX: ie6 doesn't render in a hidden DOM element
- viewer.css("display", "none");
- // View Range Controls
- range_min = $([
- '<img',
- 'src="/assets/range.gif"',
- 'style="',
- 'position: absolute;',
- 'top: '+Math.floor(preview.height/2)+'px;',
- 'left: '+NAV_RANGE_OFFSET+'px;',
- 'cursor: col-resize;',
- '"/>'].join(' ')).appendTo(viewer);
- _range_min = range_min[0];
- range_max = $([
- '<img',
- 'src="/assets/range.gif"',
- 'style="',
- 'position: absolute;',
- 'top: '+Math.floor(preview.height/2)+'px;',
- 'right: '+NAV_RANGE_OFFSET+'px;',
- 'cursor: col-resize;',
- '"/>'].join(' ')).appendTo(viewer);
- _range_max = range_max[0];
- __is_hover__ = true; //XXX: initially display range controls
- // --- DOM Events
- // ----- Left/Right Navigation Buttons
- addEvent(nav_left[0], "mousedown", nav_left_mousedown);
- addEvent(nav_right[0], "mousedown", nav_right_mousedown);
- // ----- Preview Slider
- addEvent(_slider, "mousedown", drag_init);
- _slider.onmousedown = function(e) { return cancelEvent(e); };
- // ----- Preview Pane
- addEvent(_viewer, "mousedown", drag_init);
- _viewer.onmousedown = function(e) { return cancelEvent(e); };
-
- // ----- Preview Range Controls
- container.hover(container_mouseover, container_mouseout);
- addEvent(_range_min, "mousedown", range_min_drag_init);
- _range_min.onmousedown = function(e) { return cancelEvent(e); };
- addEvent(_range_max, "mousedown", range_max_drag_init);
- _range_max.onmousedown = function(e) { return cancelEvent(e); };
- // Events Setup
- broker.subscribe("/nav/center", this, "center");
- broker.subscribe("/nav/translate", this, "translate");
- broker.subscribe("/nav/range/update", this, "update_range");
- //XXX: post init
- broker.subscribe("/nav/center", this, "post_init");
- }
- this.init = init;
- function post_init() {
- broker.unsubscribe("/nav/center", this, "post_init");
- viewer.css("display", "inline");
- slider.css("display", "inline");
- }
- this.post_init = post_init;
- // View Translation Methods
- function center() {
- // static variables
- var _overview_width = overview.width - 2; //XXX: account for border-right/left
- center = function() {
- // calculate: width
- var _num_points = parent._chart_period() * model.zoom_delta() / zoom_level[0];
- viewer_width =
- Math.max(1,
- Math.min(_overview_width,
- Math.floor(_num_points * _x_scale)));
- _viewer.style.width = viewer_width+"px";
- _slider.style.width = Math.max(NAV_SCROLL_MIN_WIDTH, viewer_width)+"px";
-
- if (viewer_width > NAV_SCROLL_MID_THRESHOLD) {
- slider_middle.css("display", "inline");
- } else {
- slider_middle.css("display", "none");
- }
- // translate
- translate(); //XXX: don't publish, has to be in-sync (otherwise, flickers)
- // update: drag modifier
- drag_modifier = (zoom_level[0] / model.zoom_delta()) * parent._x_scale() / _x_scale;
- // range controls: re-position, if necessary
- var _viewer_width = viewer.width();
- if (_viewer_width > NAV_RANGE_CENTER_THRESHOLD) {
- _range_min.style.left = NAV_RANGE_OFFSET+"px";
- if (__is_hover__) {
- range_max.css("display", "inline");
- }
- } else { // viewer.width <= range control width
- _range_min.style.left =
- Math.floor(_viewer_width/2+NAV_RANGE_CENTER_OFFSET)+"px";
- range_max.css("display", "none");
- }
- };
- return center();
- }
- this.center = center;
- function translate() {
- // static variables
- var _half_width = parent.width() / 2;
- var _overview_width = overview.width - 2;
- var _overview_max = _overview_width + overview_left;
- translate = function() {
- // shared references
- var _min_pixel = parent.min_pixel();
- var _max_pixel = parent.max_pixel();
- var _center_pixel = parent.center_pixel();
- var _real_min_pixel = _min_pixel - _half_width;
- var _real_max_pixel = _max_pixel + _half_width;
- // calculate: offset
- var _offset;
- if (_center_pixel < _max_pixel) {
- _offset = overview_left;
- if (_center_pixel > _min_pixel) {
- var _pixel_range = _real_max_pixel - _real_min_pixel;
- _offset +=
- (_center_pixel - _real_min_pixel) / _pixel_range * _overview_width;
- _offset -= (viewer_width / 2);
- _offset = Math.round(_offset);
- } // else: center_pixel == min_pixel
- } else { // center_pixel == max_pixel
- _offset = _overview_max - viewer_width;
- }
- //XXX: correct out-of-bounds
- _offset = Math.min((_overview_max - viewer_width), _offset);
- _offset = Math.max(overview_left, _offset);
- // position view and set width
- preview.style.left = (overview_left-_offset)+"px";
- _viewer.style.left = _offset+"px";
- if (viewer_width < NAV_SCROLL_MIN_WIDTH) {
- _offset -= NAV_SCROLL_MIN_WIDTH - viewer_width;
- }
- _slider.style.left =
- Math.max(overview_left, _offset)+"px";
- };
- return translate();
- }
- this.translate = translate;
- // DOM Event Methods
- // --- Left/Right Navigation
- function nav_left_mousedown(e) {
- nav_right[0].className = "nav-right-unsel nav-sel";
- nav_left[0].className = "nav-left-sel";
- addEvent(document, "mouseup", nav_left_mouseup);
- broker.publish("/chart/draw/start"); // start draw loop
- broker.publish("/chart/scroll/start", 1); // start scroll loop
- }
- function nav_left_mouseup(e) {
- nav_right[0].className = "nav-right-unsel nav-unsel";
- nav_left[0].className = "nav-left-unsel";
- removeEvent(document, "mouseup", nav_left_mouseup);
- broker.publish("/chart/draw/stop"); // stop draw loop
- broker.publish("/chart/scroll/stop"); // stop scroll loop
- }
- function nav_right_mousedown(e) {
- nav_right[0].className = "nav-right-sel nav-sel";
- addEvent(document, "mouseup", nav_right_mouseup);
- broker.publish("/chart/draw/start"); // start draw loop
- broker.publish("/chart/scroll/start", -1); // start scroll loop
- }
- function nav_right_mouseup(e) {
- nav_right[0].className = "nav-right-unsel nav-unsel";
- removeEvent(document, "mouseup", nav_right_mouseup);
- broker.publish("/chart/draw/stop"); // stop draw loop
- broker.publish("/chart/scroll/stop"); // stop scroll loop
- }
- // --- Preview Pane
- function drag_init(e) {
- e = e ? e : window.event;
-
- addEvent(document, "mousemove", drag_move);
- viewer._drag_x = e.clientX;
- addEvent(document, "mouseup", drag_end);
- broker.publish("/chart/draw/start"); // start draw loop
- }
- function drag_move(e) {
- e = e ? e : window.event;
- var delta = e.clientX - viewer._drag_x;
- if (Math.abs(delta) <= 1) {
- return cancelEvent(e);
- }
- viewer._drag_x = e.clientX;
- delta = -1 * Math.floor(drag_modifier * delta);
- broker.publish("/chart/translate", delta);
- return cancelEvent(e);
- }
- function drag_end(e) {
- removeEvent(document, "mousemove", drag_move);
- removeEvent(document, "mouseup", drag_end);
- broker.publish("/chart/draw/stop"); // stop draw loop
- }
- // Preview Range Control
- function container_mouseover(e) {
- __is_hover__ = true;
- range_min.css("display", "inline");
- if (viewer.width() > NAV_RANGE_CENTER_THRESHOLD) {
- range_max.css("display", "inline");
- }
- }
- function container_mouseout(e) {
- __is_hover__ = false;
- range_min.css("display", "none");
- range_max.css("display", "none");
- }
- function range_min_drag_init(e) {
- __is_drag_min__ = true;
- _range_drag_init(e);
- }
- function range_max_drag_init(e) {
- __is_drag_min__ = false;
-
- _range_drag_init(e);
- }
- function _range_drag_init(e) {
- e = e ? e : window.event;
- addEvent(document, "mousemove", range_drag_move);
- viewer._range_x = e.clientX;
- addEvent(document, "mouseup", range_drag_end);
- broker.publish("/chart/draw/start"); // start draw loop
- }
- function update_range(modified, fixed) {
- // static variables
- var _overview_width = overview.width-2;
- var _zoom_schema = model.zoom_schema();
- var _visible_panes = parent.width() / parent._pane_width();
- var _half_width = Math.floor(parent.width() / 2);
- update_range = function(modified, fixed) {
- // sanitize parameters
- var _min = Math.min(modified, fixed);
- var _max = Math.max(modified, fixed);
- var _min_fixed = _min === fixed ? true : false;
- var _rel_min = Math.max(0, _min - overview_left);
- var _rel_max = Math.min(_overview_width, _max - overview_left);
- // calculate new time
- var _rel_range = (_rel_max - _rel_min) / _overview_width;
- var _time_range = Math.floor(model._time_range() * _rel_range);
-
- // determine new zoom/period
- var _zoom_level, _zoom_scale;
- for (var i=0; i<_zoom_schema.length; i++) {
- _zoom_scale = i;
- _zoom_level = _zoom_schema[_zoom_scale];
- if ((_zoom_level[0] * _zoom_level[2] * _visible_panes) >= _time_range) {
- break;
- }
- }
- var _pane_period, _zoom_time_range;
- for (i=_zoom_level[1]; i<=_zoom_level[2]; i++) {
- _pane_period = i;
- _zoom_time_range = _zoom_level[0] * i * _visible_panes;
- if (_zoom_time_range >= _time_range) {
- break;
- }
- }
- var _half_time = _zoom_time_range / 2;
- var _time_offset;
- if (_min_fixed) {
- _time_offset = model.min_time();
- _time_offset += model._time_range() * (_rel_min / _overview_width);
- _time_offset += _half_time;
- } else {
- _time_offset = model.min_time();
- _time_offset += model._time_range() * (_rel_max / _overview_width);
- _time_offset -= _half_time;
- }
- _time_offset = Math.floor(_time_offset);
- parent._update_zoom(_zoom_scale, _pane_period);
- parent.center(_time_offset, _half_width);
- };
- return update_range(modified, fixed);
- }
- this.update_range = update_range;
- function range_drag_move(e) {
- e = e ? e : window.event;
- if (Math.abs(e.clientX - viewer._range_x) <= 1) {
- return cancelEvent(e);
- }
- viewer._range_x = e.clientX;
- var _viewer_left = parseInt(_viewer.style.left, 10);
- var delta;
- //XXX: use a global to switch, because events can be queued up
- if (__is_drag_min__) { // dragging range min
- delta = viewer.offset().left - e.clientX;
- var _new_min = _viewer_left-delta;
- var _max = _viewer_left+viewer.width();
- if (_new_min > _max) {
- __is_drag_min__ = false;
- return cancelEvent(e);
- }
- broker.publish("/nav/range/update", _new_min, _max);
- } else { // draggging range max
- var _viewer_width = viewer.width();
- delta = (viewer.offset().left+_viewer_width) - e.clientX;
- var _new_max = _viewer_left+_viewer_width-delta;
- if (_new_max < _viewer_left) {
- __is_drag_min__ = true;
- return cancelEvent(e);
- }
- broker.publish("/nav/range/update", _new_max, _viewer_left);
- }
- return cancelEvent(e);
- }
- function range_drag_end(e) {
- removeEvent(document, "mousemove", range_drag_move);
- removeEvent(document, "mouseup", range_drag_end);
- broker.publish("/chart/draw/stop"); // stop draw loop
- }
- };
- // ---------- Chart ----------
- var LARGE_CHART_THRESHOLD_WIDTH = 720; // target: 720px x 6 panes @ 120px
- var LARGE_CHART_NUM_PANES = 6;
- var SMALL_CHART_NUM_PANES = 3; // target: 180px x 3 panes @ 60px
- var BORDER_OFFSET_X = 1;
- var BORDER_OFFSET_Y = 1;
- var TOP_PANE_HEIGHT = 25;
- var TOP_PANE_BORDER_COLOR = "rgb(175,175,175)"; //XXX: consider: 150
- var UPDATE_DELTA_FACTOR = 0.5; //XXX: speed at which updates will improve
- var UPDATE_BOUND = 1000 / 25; //XXX: lower bound on re-draw interval
- var UPDATE_AFTER_STOP = 1000 * 0.5; //XXX: allow a second between stop/start drag
- if ($.browser.msie) { // IE6 needs help
- UPDATE_BOUND = 4 * 1000;
- UPDATE_AFTER_STOP = 1000;
- }
- var Y_SCALE_FACTOR = 1.10;
- var Y_LABELS_WIDTH = 100;
- var Y_LABELS_RIGHT_OFFSET = 4;
- var Y_LABEL_OFFSET = -12;
- var Y_LABEL = "W"; //XXX: make this a parameter
- var MOUSE_SCROLL_ZOOM_FACTOR = 0.5;
- var MOUSE_SCROLL_ZOOM_DELTA_MAX = 2;
- if ($.browser.msie) { // IE6 needs help
- MOUSE_SCROLL_ZOOM_FACTOR = 0.75;
- }
- var TICK_MARK_INCREMENTS = [
- 60,
- 60*5,
- 60*10,
- 60*15,
- 60*30,
- 60*60,
- 60*60*3,
- 60*60*6,
- 60*60*12,
- 60*60*24,
- 60*60*24*7,
- 60*60*24*14,
- 60*60*24*31
- ];
- //FIXME: take into account: Date.getTimezoneOffset() [returns offset in minutes]
- var TICK_DATE_PROXY = new Date();
- var _format_date = function(date_) {
- if (date_.getSeconds() !== 0) {
- return formatDate(date_, "h:mm:ss a");
- }
- if (date_.getMinutes() !== 0) {
- return formatDate(date_, "h:mm a");
- }
-
- if (date_.getHours() !== 0) {
- return formatDate(date_, "h a");
- }
- if (date_.getDay() !== 1) {
- return formatDate(date_, "MMM d");
- }
- if (date_.getMonth() !== 0) {
- return formatDate(date_, "MMM");
- }
- return formatDate(date_, "yyyy");
- };
- ;;; $c._format_date = _format_date;
- var TICK_1_MINUTE = function(timestamp_) {
- TICK_DATE_PROXY.setTime(timestamp_*1000);
- if (TICK_DATE_PROXY.getSeconds() !== 0) {
- TICK_DATE_PROXY.setSeconds(60);
- }
- return [
- (TICK_DATE_PROXY.getTime() / 1000) - timestamp_,
- _format_date(TICK_DATE_PROXY)
- ];
- };
- ;;; $c…
Large files files are truncated, but you can click here to view the full file