UNPKG

rickshaw

Version:

JavaScript toolkit for creating interactive real-time graphs

1,983 lines (1,560 loc) 108 kB
(function (root, factory) { if (typeof define === 'function' && define.amd) { define(['d3'], function (d3) { return (root.Rickshaw = factory(d3)); }); } else if (typeof exports === 'object') { module.exports = factory(require('d3')); } else { root.Rickshaw = factory(d3); } }(this, function (d3) { /* jshint -W079 */ var Rickshaw = { version: '1.7.1', namespace: function(namespace, obj) { var parts = namespace.split('.'); var parent = Rickshaw; for(var i = 1, length = parts.length; i < length; i++) { var currentPart = parts[i]; parent[currentPart] = parent[currentPart] || {}; parent = parent[currentPart]; } return parent; }, keys: function(obj) { var keys = []; for (var key in obj) keys.push(key); return keys; }, extend: function(destination, source) { for (var property in source) { destination[property] = source[property]; } return destination; }, clone: function(obj) { return JSON.parse(JSON.stringify(obj)); } }; /* Adapted from https://github.com/Jakobo/PTClass */ /* Copyright (c) 2005-2010 Sam Stephenson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /* Based on Alex Arnell's inheritance implementation. */ /** section: Language * class Class * * Manages Prototype's class-based OOP system. * * Refer to Prototype's web site for a [tutorial on classes and * inheritance](http://prototypejs.org/learn/class-inheritance). **/ (function(globalContext) { /* ------------------------------------ */ /* Import from object.js */ /* ------------------------------------ */ var _toString = Object.prototype.toString, NULL_TYPE = 'Null', UNDEFINED_TYPE = 'Undefined', BOOLEAN_TYPE = 'Boolean', NUMBER_TYPE = 'Number', STRING_TYPE = 'String', OBJECT_TYPE = 'Object', FUNCTION_CLASS = '[object Function]'; function isFunction(object) { return _toString.call(object) === FUNCTION_CLASS; } function extend(destination, source) { for (var property in source) if (source.hasOwnProperty(property)) // modify protect primitive slaughter destination[property] = source[property]; return destination; } function keys(object) { if (Type(object) !== OBJECT_TYPE) { throw new TypeError(); } var results = []; for (var property in object) { if (object.hasOwnProperty(property)) { results.push(property); } } return results; } function Type(o) { switch(o) { case null: return NULL_TYPE; case (void 0): return UNDEFINED_TYPE; } var type = typeof o; switch(type) { case 'boolean': return BOOLEAN_TYPE; case 'number': return NUMBER_TYPE; case 'string': return STRING_TYPE; } return OBJECT_TYPE; } function isUndefined(object) { return typeof object === "undefined"; } /* ------------------------------------ */ /* Import from Function.js */ /* ------------------------------------ */ var slice = Array.prototype.slice; function argumentNames(fn) { var names = fn.toString().match(/^[\s\(]*function[^(]*\(([^)]*)\)/)[1] .replace(/\/\/.*?[\r\n]|\/\*(?:.|[\r\n])*?\*\//g, '') .replace(/\s+/g, '').split(','); return names.length == 1 && !names[0] ? [] : names; } function wrap(fn, wrapper) { var __method = fn; return function() { var a = update([bind(__method, this)], arguments); return wrapper.apply(this, a); } } function update(array, args) { var arrayLength = array.length, length = args.length; while (length--) array[arrayLength + length] = args[length]; return array; } function merge(array, args) { array = slice.call(array, 0); return update(array, args); } function bind(fn, context) { if (arguments.length < 2 && isUndefined(arguments[0])) return this; var __method = fn, args = slice.call(arguments, 2); return function() { var a = merge(args, arguments); return __method.apply(context, a); } } /* ------------------------------------ */ /* Import from Prototype.js */ /* ------------------------------------ */ var emptyFunction = function(){}; var Class = (function() { // Some versions of JScript fail to enumerate over properties, names of which // correspond to non-enumerable properties in the prototype chain var IS_DONTENUM_BUGGY = (function(){ for (var p in { toString: 1 }) { // check actual property name, so that it works with augmented Object.prototype if (p === 'toString') return false; } return true; })(); function subclass() {}; function create() { var parent = null, properties = [].slice.apply(arguments); if (isFunction(properties[0])) parent = properties.shift(); function klass() { this.initialize.apply(this, arguments); } extend(klass, Class.Methods); klass.superclass = parent; klass.subclasses = []; if (parent) { subclass.prototype = parent.prototype; klass.prototype = new subclass; try { parent.subclasses.push(klass) } catch(e) {} } for (var i = 0, length = properties.length; i < length; i++) klass.addMethods(properties[i]); if (!klass.prototype.initialize) klass.prototype.initialize = emptyFunction; klass.prototype.constructor = klass; return klass; } function addMethods(source) { var ancestor = this.superclass && this.superclass.prototype, properties = keys(source); // IE6 doesn't enumerate `toString` and `valueOf` (among other built-in `Object.prototype`) properties, // Force copy if they're not Object.prototype ones. // Do not copy other Object.prototype.* for performance reasons if (IS_DONTENUM_BUGGY) { if (source.toString != Object.prototype.toString) properties.push("toString"); if (source.valueOf != Object.prototype.valueOf) properties.push("valueOf"); } for (var i = 0, length = properties.length; i < length; i++) { var property = properties[i], value = source[property]; if (ancestor && isFunction(value) && argumentNames(value)[0] == "$super") { var method = value; value = wrap((function(m) { return function() { return ancestor[m].apply(this, arguments); }; })(property), method); value.valueOf = bind(method.valueOf, method); value.toString = bind(method.toString, method); } this.prototype[property] = value; } return this; } return { create: create, Methods: { addMethods: addMethods } }; })(); if (globalContext.exports) { globalContext.exports.Class = Class; } else { globalContext.Class = Class; } })(Rickshaw); Rickshaw.namespace('Rickshaw.Compat.ClassList'); Rickshaw.Compat.ClassList = function() { /* adapted from http://purl.eligrey.com/github/classList.js/blob/master/classList.js */ if (typeof document !== "undefined" && !("classList" in document.createElement("a"))) { (function (view) { "use strict"; var classListProp = "classList" , protoProp = "prototype" , elemCtrProto = (view.HTMLElement || view.Element)[protoProp] , objCtr = Object , strTrim = String[protoProp].trim || function () { return this.replace(/^\s+|\s+$/g, ""); } , arrIndexOf = Array[protoProp].indexOf || function (item) { var i = 0 , len = this.length ; for (; i < len; i++) { if (i in this && this[i] === item) { return i; } } return -1; } // Vendors: please allow content code to instantiate DOMExceptions , DOMEx = function (type, message) { this.name = type; this.code = DOMException[type]; this.message = message; } , checkTokenAndGetIndex = function (classList, token) { if (token === "") { throw new DOMEx( "SYNTAX_ERR" , "An invalid or illegal string was specified" ); } if (/\s/.test(token)) { throw new DOMEx( "INVALID_CHARACTER_ERR" , "String contains an invalid character" ); } return arrIndexOf.call(classList, token); } , ClassList = function (elem) { var trimmedClasses = strTrim.call(elem.className) , classes = trimmedClasses ? trimmedClasses.split(/\s+/) : [] , i = 0 , len = classes.length ; for (; i < len; i++) { this.push(classes[i]); } this._updateClassName = function () { elem.className = this.toString(); }; } , classListProto = ClassList[protoProp] = [] , classListGetter = function () { return new ClassList(this); } ; // Most DOMException implementations don't allow calling DOMException's toString() // on non-DOMExceptions. Error's toString() is sufficient here. DOMEx[protoProp] = Error[protoProp]; classListProto.item = function (i) { return this[i] || null; }; classListProto.contains = function (token) { token += ""; return checkTokenAndGetIndex(this, token) !== -1; }; classListProto.add = function (token) { token += ""; if (checkTokenAndGetIndex(this, token) === -1) { this.push(token); this._updateClassName(); } }; classListProto.remove = function (token) { token += ""; var index = checkTokenAndGetIndex(this, token); if (index !== -1) { this.splice(index, 1); this._updateClassName(); } }; classListProto.toggle = function (token) { token += ""; if (checkTokenAndGetIndex(this, token) === -1) { this.add(token); } else { this.remove(token); } }; classListProto.toString = function () { return this.join(" "); }; if (objCtr.defineProperty) { var classListPropDesc = { get: classListGetter , enumerable: true , configurable: true }; try { objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc); } catch (ex) { // IE 8 doesn't support enumerable:true if (ex.number === -0x7FF5EC54) { classListPropDesc.enumerable = false; objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc); } } } else if (objCtr[protoProp].__defineGetter__) { elemCtrProto.__defineGetter__(classListProp, classListGetter); } }(window)); } }; if ( (typeof RICKSHAW_NO_COMPAT !== "undefined" && !RICKSHAW_NO_COMPAT) || typeof RICKSHAW_NO_COMPAT === "undefined") { new Rickshaw.Compat.ClassList(); } Rickshaw.namespace('Rickshaw.Graph'); Rickshaw.Graph = function(args) { var self = this; this.initialize = function(args) { if (!args.element) throw "Rickshaw.Graph needs a reference to an element"; if (args.element.nodeType !== 1) throw "Rickshaw.Graph element was defined but not an HTML element"; this.element = args.element; this.series = args.series; this.window = {}; this.updateCallbacks = []; this.configureCallbacks = []; this.defaults = { interpolation: 'cardinal', offset: 'zero', min: undefined, max: undefined, preserve: false, xScale: undefined, yScale: undefined, stack: true }; this._loadRenderers(); this.configure(args); this.setSeries(args.series); this.setSize({ width: args.width, height: args.height }); this.element.classList.add('rickshaw_graph'); this.vis = d3.select(this.element) .append("svg:svg") .attr('width', this.width) .attr('height', this.height); this.discoverRange(); }; this._loadRenderers = function() { for (var name in Rickshaw.Graph.Renderer) { if (!name || !Rickshaw.Graph.Renderer.hasOwnProperty(name)) continue; var r = Rickshaw.Graph.Renderer[name]; if (!r || !r.prototype || !r.prototype.render) continue; self.registerRenderer(new r( { graph: self } )); } }; this.validateSeries = function(series) { if (!Array.isArray(series) && !(series instanceof Rickshaw.Series)) { var seriesSignature = Object.prototype.toString.apply(series); throw "series is not an array: " + seriesSignature; } var pointsCount; series.forEach( function(s) { if (!(s instanceof Object)) { throw "series element is not an object: " + s; } if (!(s.data)) { throw "series has no data: " + JSON.stringify(s); } if (!Array.isArray(s.data)) { throw "series data is not an array: " + JSON.stringify(s.data); } if (s.data.length > 0) { var x = s.data[0].x; var y = s.data[0].y; if (typeof x != 'number' || ( typeof y != 'number' && y !== null ) ) { throw "x and y properties of points should be numbers instead of " + (typeof x) + " and " + (typeof y); } } if (s.data.length >= 3) { // probe to sanity check sort order if (s.data[2].x < s.data[1].x || s.data[1].x < s.data[0].x || s.data[s.data.length - 1].x < s.data[0].x) { throw "series data needs to be sorted on x values for series name: " + s.name; } } }, this ); }; this.setSeries = function(series) { this.validateSeries(series); this.series = series; this.series.active = function() { return self.series.filter( function(s) { return !s.disabled } ) }; }; this.dataDomain = function() { var data = this.series.map( function(s) { return s.data } ); var min = d3.min( data.map( function(d) { return d[0].x } ) ); var max = d3.max( data.map( function(d) { return d[d.length - 1].x } ) ); return [min, max]; }; this.discoverRange = function() { var domain = this.renderer.domain(); // this.*Scale is coming from the configuration dictionary // which may be referenced by the Graph creator, or shared // with other Graphs. We need to ensure we copy the scale // so that our mutations do not change the object given to us. // Hence the .copy() this.x = (this.xScale || d3.scale.linear()).copy().domain(domain.x).range([0, this.width]); this.y = (this.yScale || d3.scale.linear()).copy().domain(domain.y).range([this.height, 0]); this.x.magnitude = d3.scale.linear() .domain([domain.x[0] - domain.x[0], domain.x[1] - domain.x[0]]) .range([0, this.width]); this.y.magnitude = d3.scale.linear() .domain([domain.y[0] - domain.y[0], domain.y[1] - domain.y[0]]) .range([0, this.height]); }; this.render = function() { var stackedData = this.stackData(); this.discoverRange(); this.renderer.render(); this.updateCallbacks.forEach( function(callback) { callback(); } ); }; this.update = this.render; this.stackData = function() { var data = this.series.active() .map( function(d) { return d.data } ) .map( function(d) { return d.filter( function(d) { return this._slice(d) }, this ) }, this); var preserve = this.preserve; if (!preserve) { this.series.forEach( function(series) { if (series.scale) { // data must be preserved when a scale is used preserve = true; } } ); } data = preserve ? Rickshaw.clone(data) : data; this.series.active().forEach( function(series, index) { if (series.scale) { // apply scale to each series var seriesData = data[index]; if(seriesData) { seriesData.forEach( function(d) { d.y = series.scale(d.y); } ); } } } ); this.stackData.hooks.data.forEach( function(entry) { data = entry.f.apply(self, [data]); } ); var stackedData; if (!this.renderer.unstack) { this._validateStackable(); var layout = d3.layout.stack(); layout.offset( self.offset ); stackedData = layout(data); } stackedData = stackedData || data; if (this.renderer.unstack) { stackedData.forEach( function(seriesData) { seriesData.forEach( function(d) { d.y0 = d.y0 === undefined ? 0 : d.y0; } ); } ); } this.stackData.hooks.after.forEach( function(entry) { stackedData = entry.f.apply(self, [data]); } ); var i = 0; this.series.forEach( function(series) { if (series.disabled) return; series.stack = stackedData[i++]; } ); this.stackedData = stackedData; return stackedData; }; this._validateStackable = function() { var series = this.series; var pointsCount; series.forEach( function(s) { pointsCount = pointsCount || s.data.length; if (pointsCount && s.data.length != pointsCount) { throw "stacked series cannot have differing numbers of points: " + pointsCount + " vs " + s.data.length + "; see Rickshaw.Series.fill()"; } }, this ); }; this.stackData.hooks = { data: [], after: [] }; this._slice = function(d) { if (this.window.xMin || this.window.xMax) { var isInRange = true; if (this.window.xMin && d.x < this.window.xMin) isInRange = false; if (this.window.xMax && d.x > this.window.xMax) isInRange = false; return isInRange; } return true; }; this.onUpdate = function(callback) { this.updateCallbacks.push(callback); }; this.onConfigure = function(callback) { this.configureCallbacks.push(callback); }; this.registerRenderer = function(renderer) { this._renderers = this._renderers || {}; this._renderers[renderer.name] = renderer; }; this.configure = function(args) { this.config = this.config || {}; if (args.width || args.height) { this.setSize(args); } Rickshaw.keys(this.defaults).forEach( function(k) { this.config[k] = k in args ? args[k] : k in this ? this[k] : this.defaults[k]; }, this ); Rickshaw.keys(this.config).forEach( function(k) { this[k] = this.config[k]; }, this ); if ('stack' in args) args.unstack = !args.stack; var renderer = args.renderer || (this.renderer && this.renderer.name) || 'stack'; this.setRenderer(renderer, args); this.configureCallbacks.forEach( function(callback) { callback(args); } ); }; this.setRenderer = function(r, args) { if (typeof r == 'function') { this.renderer = new r( { graph: self } ); this.registerRenderer(this.renderer); } else { if (!this._renderers[r]) { throw "couldn't find renderer " + r; } this.renderer = this._renderers[r]; } if (typeof args == 'object') { this.renderer.configure(args); } }; this.setSize = function(args) { args = args || {}; if (args.width && args.height) { // use explicitly specified size this.width = args.width; this.height = args.height; } else { // calc size (will cause layout reflow) if (typeof window !== 'undefined') { var style = window.getComputedStyle(this.element, null); var elementWidth = parseInt(style.getPropertyValue('width'), 10); var elementHeight = parseInt(style.getPropertyValue('height'), 10); } this.width = args.width || elementWidth || 400; this.height = args.height || elementHeight || 250; } this.vis && this.vis .attr('width', this.width) .attr('height', this.height); }; this.initialize(args); }; Rickshaw.namespace('Rickshaw.Fixtures.Color'); Rickshaw.Fixtures.Color = function() { this.schemes = {}; this.schemes.spectrum14 = [ '#ecb796', '#dc8f70', '#b2a470', '#92875a', '#716c49', '#d2ed82', '#bbe468', '#a1d05d', '#e7cbe6', '#d8aad6', '#a888c2', '#9dc2d3', '#649eb9', '#387aa3' ].reverse(); this.schemes.spectrum2000 = [ '#57306f', '#514c76', '#646583', '#738394', '#6b9c7d', '#84b665', '#a7ca50', '#bfe746', '#e2f528', '#fff726', '#ecdd00', '#d4b11d', '#de8800', '#de4800', '#c91515', '#9a0000', '#7b0429', '#580839', '#31082b' ]; this.schemes.spectrum2001 = [ '#2f243f', '#3c2c55', '#4a3768', '#565270', '#6b6b7c', '#72957f', '#86ad6e', '#a1bc5e', '#b8d954', '#d3e04e', '#ccad2a', '#cc8412', '#c1521d', '#ad3821', '#8a1010', '#681717', '#531e1e', '#3d1818', '#320a1b' ]; this.schemes.classic9 = [ '#423d4f', '#4a6860', '#848f39', '#a2b73c', '#ddcb53', '#c5a32f', '#7d5836', '#963b20', '#7c2626', '#491d37', '#2f254a' ].reverse(); this.schemes.httpStatus = { 503: '#ea5029', 502: '#d23f14', 500: '#bf3613', 410: '#efacea', 409: '#e291dc', 403: '#f457e8', 408: '#e121d2', 401: '#b92dae', 405: '#f47ceb', 404: '#a82a9f', 400: '#b263c6', 301: '#6fa024', 302: '#87c32b', 307: '#a0d84c', 304: '#28b55c', 200: '#1a4f74', 206: '#27839f', 201: '#52adc9', 202: '#7c979f', 203: '#a5b8bd', 204: '#c1cdd1' }; this.schemes.colorwheel = [ '#b5b6a9', '#858772', '#785f43', '#96557e', '#4682b4', '#65b9ac', '#73c03a', '#cb513a' ].reverse(); this.schemes.cool = [ '#5e9d2f', '#73c03a', '#4682b4', '#7bc3b8', '#a9884e', '#c1b266', '#a47493', '#c09fb5' ]; this.schemes.munin = [ '#00cc00', '#0066b3', '#ff8000', '#ffcc00', '#330099', '#990099', '#ccff00', '#ff0000', '#808080', '#008f00', '#00487d', '#b35a00', '#b38f00', '#6b006b', '#8fb300', '#b30000', '#bebebe', '#80ff80', '#80c9ff', '#ffc080', '#ffe680', '#aa80ff', '#ee00cc', '#ff8080', '#666600', '#ffbfff', '#00ffcc', '#cc6699', '#999900' ]; }; Rickshaw.namespace('Rickshaw.Fixtures.RandomData'); Rickshaw.Fixtures.RandomData = function(timeInterval) { var addData; timeInterval = timeInterval || 1; var lastRandomValue = 200; var timeBase = Math.floor(new Date().getTime() / 1000); this.addData = function(data) { var randomValue = Math.random() * 100 + 15 + lastRandomValue; var index = data[0].length; var counter = 1; data.forEach( function(series) { var randomVariance = Math.random() * 20; var v = randomValue / 25 + counter++ + (Math.cos((index * counter * 11) / 960) + 2) * 15 + (Math.cos(index / 7) + 2) * 7 + (Math.cos(index / 17) + 2) * 1; series.push( { x: (index * timeInterval) + timeBase, y: v + randomVariance } ); } ); lastRandomValue = randomValue * 0.85; }; this.removeData = function(data) { data.forEach( function(series) { series.shift(); } ); timeBase += timeInterval; }; }; Rickshaw.namespace('Rickshaw.Fixtures.Time'); Rickshaw.Fixtures.Time = function() { var self = this; this.months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; this.units = [ { name: 'decade', seconds: 86400 * 365.25 * 10, formatter: function(d) { return (parseInt(d.getUTCFullYear() / 10, 10) * 10) } }, { name: 'year', seconds: 86400 * 365.25, formatter: function(d) { return d.getUTCFullYear() } }, { name: 'month', seconds: 86400 * 30.5, formatter: function(d) { return self.months[d.getUTCMonth()] } }, { name: 'week', seconds: 86400 * 7, formatter: function(d) { return self.formatDate(d) } }, { name: 'day', seconds: 86400, formatter: function(d) { return d.getUTCDate() } }, { name: '6 hour', seconds: 3600 * 6, formatter: function(d) { return self.formatTime(d) } }, { name: 'hour', seconds: 3600, formatter: function(d) { return self.formatTime(d) } }, { name: '15 minute', seconds: 60 * 15, formatter: function(d) { return self.formatTime(d) } }, { name: 'minute', seconds: 60, formatter: function(d) { return d.getUTCMinutes() + 'm' } }, { name: '15 second', seconds: 15, formatter: function(d) { return d.getUTCSeconds() + 's' } }, { name: 'second', seconds: 1, formatter: function(d) { return d.getUTCSeconds() + 's' } }, { name: 'decisecond', seconds: 1/10, formatter: function(d) { return d.getUTCMilliseconds() + 'ms' } }, { name: 'centisecond', seconds: 1/100, formatter: function(d) { return d.getUTCMilliseconds() + 'ms' } } ]; this.unit = function(unitName) { return this.units.filter( function(unit) { return unitName == unit.name } ).shift(); }; this.formatDate = function(d) { return d3.time.format('%b %e')(d); }; this.formatTime = function(d) { return d.toUTCString().match(/(\d+:\d+):/)[1]; }; this.ceil = function(time, unit) { var date, floor, year; if (unit.name == 'month') { date = new Date(time * 1000); floor = Date.UTC(date.getUTCFullYear(), date.getUTCMonth()) / 1000; if (floor == time) return time; year = date.getUTCFullYear(); var month = date.getUTCMonth(); if (month == 11) { month = 0; year = year + 1; } else { month += 1; } return Date.UTC(year, month) / 1000; } if (unit.name == 'year') { date = new Date(time * 1000); floor = Date.UTC(date.getUTCFullYear(), 0) / 1000; if (floor == time) return time; year = date.getUTCFullYear() + 1; return Date.UTC(year, 0) / 1000; } return Math.ceil(time / unit.seconds) * unit.seconds; }; }; Rickshaw.namespace('Rickshaw.Fixtures.Time.Local'); Rickshaw.Fixtures.Time.Local = function() { var self = this; this.months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; this.units = [ { name: 'decade', seconds: 86400 * 365.25 * 10, formatter: function(d) { return (parseInt(d.getFullYear() / 10, 10) * 10) } }, { name: 'year', seconds: 86400 * 365.25, formatter: function(d) { return d.getFullYear() } }, { name: 'month', seconds: 86400 * 30.5, formatter: function(d) { return self.months[d.getMonth()] } }, { name: 'week', seconds: 86400 * 7, formatter: function(d) { return self.formatDate(d) } }, { name: 'day', seconds: 86400, formatter: function(d) { return d.getDate() } }, { name: '6 hour', seconds: 3600 * 6, formatter: function(d) { return self.formatTime(d) } }, { name: 'hour', seconds: 3600, formatter: function(d) { return self.formatTime(d) } }, { name: '15 minute', seconds: 60 * 15, formatter: function(d) { return self.formatTime(d) } }, { name: 'minute', seconds: 60, formatter: function(d) { return d.getMinutes() } }, { name: '15 second', seconds: 15, formatter: function(d) { return d.getSeconds() + 's' } }, { name: 'second', seconds: 1, formatter: function(d) { return d.getSeconds() + 's' } }, { name: 'decisecond', seconds: 1/10, formatter: function(d) { return d.getMilliseconds() + 'ms' } }, { name: 'centisecond', seconds: 1/100, formatter: function(d) { return d.getMilliseconds() + 'ms' } } ]; this.unit = function(unitName) { return this.units.filter( function(unit) { return unitName == unit.name } ).shift(); }; this.formatDate = function(d) { return d3.time.format('%b %e')(d); }; this.formatTime = function(d) { return d.toString().match(/(\d+:\d+):/)[1]; }; this.ceil = function(time, unit) { var date, floor, year, offset; if (unit.name == 'day') { var nearFuture = new Date((time + unit.seconds - 1) * 1000); var rounded = new Date(0); rounded.setFullYear(nearFuture.getFullYear()); rounded.setMonth(nearFuture.getMonth()); rounded.setDate(nearFuture.getDate()); rounded.setMilliseconds(0); rounded.setSeconds(0); rounded.setMinutes(0); rounded.setHours(0); return rounded.getTime() / 1000; } if (unit.name == 'month') { date = new Date(time * 1000); floor = new Date(date.getFullYear(), date.getMonth()).getTime() / 1000; if (floor == time) return time; year = date.getFullYear(); var month = date.getMonth(); if (month == 11) { month = 0; year = year + 1; } else { month += 1; } return new Date(year, month).getTime() / 1000; } if (unit.name == 'year') { date = new Date(time * 1000); floor = new Date(date.getUTCFullYear(), 0).getTime() / 1000; if (floor == time) return time; year = date.getFullYear() + 1; return new Date(year, 0).getTime() / 1000; } offset = new Date(time * 1000).getTimezoneOffset() * 60; return Math.ceil((time - offset) / unit.seconds) * unit.seconds + offset; }; }; Rickshaw.namespace('Rickshaw.Fixtures.Number'); Rickshaw.Fixtures.Number.formatKMBT = function(y) { var abs_y = Math.abs(y); if (abs_y >= 1000000000000) { return (y / 1000000000000).toFixed(2) + "T" } else if (abs_y >= 1000000000) { return (y / 1000000000).toFixed(2) + "B" } else if (abs_y >= 1000000) { return (y / 1000000).toFixed(2) + "M" } else if (abs_y >= 1000) { return (y / 1000).toFixed(2) + "K" } else if (abs_y < 1 && abs_y > 0) { return y.toFixed(2) } else if (abs_y === 0) { return '0' } else { return y } }; Rickshaw.Fixtures.Number.formatBase1024KMGTP = function(y) { var abs_y = Math.abs(y); if (abs_y >= 1125899906842624) { return (y / 1125899906842624).toFixed(2) + "P" } else if (abs_y >= 1099511627776) { return (y / 1099511627776).toFixed(2) + "T" } else if (abs_y >= 1073741824) { return (y / 1073741824).toFixed(2) + "G" } else if (abs_y >= 1048576) { return (y / 1048576).toFixed(2) + "M" } else if (abs_y >= 1024) { return (y / 1024).toFixed(2) + "K" } else if (abs_y < 1 && abs_y > 0) { return y.toFixed(2) } else if (abs_y === 0) { return '0' } else { return y } }; Rickshaw.namespace("Rickshaw.Color.Palette"); Rickshaw.Color.Palette = function(args) { var color = new Rickshaw.Fixtures.Color(); args = args || {}; this.schemes = {}; this.scheme = color.schemes[args.scheme] || args.scheme || color.schemes.colorwheel; this.runningIndex = 0; this.generatorIndex = 0; if (args.interpolatedStopCount) { var schemeCount = this.scheme.length - 1; var i, j, scheme = []; for (i = 0; i < schemeCount; i++) { scheme.push(this.scheme[i]); var generator = d3.interpolateHsl(this.scheme[i], this.scheme[i + 1]); for (j = 1; j < args.interpolatedStopCount; j++) { scheme.push(generator((1 / args.interpolatedStopCount) * j)); } } scheme.push(this.scheme[this.scheme.length - 1]); this.scheme = scheme; } this.rotateCount = this.scheme.length; this.color = function(key) { return this.scheme[key] || this.scheme[this.runningIndex++] || this.interpolateColor() || '#808080'; }; this.interpolateColor = function() { if (!Array.isArray(this.scheme)) return; var color; if (this.generatorIndex == this.rotateCount * 2 - 1) { color = d3.interpolateHsl(this.scheme[this.generatorIndex], this.scheme[0])(0.5); this.generatorIndex = 0; this.rotateCount *= 2; } else { color = d3.interpolateHsl(this.scheme[this.generatorIndex], this.scheme[this.generatorIndex + 1])(0.5); this.generatorIndex++; } this.scheme.push(color); return color; }; }; Rickshaw.namespace('Rickshaw.Graph.Ajax'); Rickshaw.Graph.Ajax = Rickshaw.Class.create( { initialize: function(args) { this.dataURL = args.dataURL; this.onData = args.onData || function(d) { return d }; this.onComplete = args.onComplete || function() {}; this.onError = args.onError || function() {}; this.args = args; // pass through to Rickshaw.Graph this.request(); }, request: function() { jQuery.ajax( { url: this.dataURL, dataType: 'json', success: this.success.bind(this), error: this.error.bind(this) } ); }, error: function() { console.log("error loading dataURL: " + this.dataURL); this.onError(this); }, success: function(data, status) { data = this.onData(data); this.args.series = this._splice({ data: data, series: this.args.series }); this.graph = this.graph || new Rickshaw.Graph(this.args); this.graph.render(); this.onComplete(this); }, _splice: function(args) { var data = args.data; var series = args.series; if (!args.series) return data; series.forEach( function(s) { var seriesKey = s.key || s.name; if (!seriesKey) throw "series needs a key or a name"; data.forEach( function(d) { var dataKey = d.key || d.name; if (!dataKey) throw "data needs a key or a name"; if (seriesKey == dataKey) { var properties = ['color', 'name', 'data']; properties.forEach( function(p) { if (d[p]) s[p] = d[p]; } ); } } ); } ); return series; } } ); Rickshaw.namespace('Rickshaw.Graph.Annotate'); Rickshaw.Graph.Annotate = function(args) { var graph = this.graph = args.graph; this.elements = { timeline: args.element }; var self = this; this.data = {}; this.elements.timeline.classList.add('rickshaw_annotation_timeline'); this.add = function(time, content, end_time) { self.data[time] = self.data[time] || {'boxes': []}; self.data[time].boxes.push({content: content, end: end_time}); }; this.update = function() { Rickshaw.keys(self.data).forEach( function(time) { var annotation = self.data[time]; var left = self.graph.x(time); if (left < 0 || left > self.graph.x.range()[1]) { if (annotation.element) { annotation.line.classList.add('offscreen'); annotation.element.style.display = 'none'; } annotation.boxes.forEach( function(box) { if ( box.rangeElement ) box.rangeElement.classList.add('offscreen'); }); return; } if (!annotation.element) { var element = annotation.element = document.createElement('div'); element.classList.add('annotation'); this.elements.timeline.appendChild(element); element.addEventListener('click', function(e) { element.classList.toggle('active'); annotation.line.classList.toggle('active'); annotation.boxes.forEach( function(box) { if ( box.rangeElement ) box.rangeElement.classList.toggle('active'); }); }, false); } annotation.element.style.left = left + 'px'; annotation.element.style.display = 'block'; annotation.boxes.forEach( function(box) { var element = box.element; if (!element) { element = box.element = document.createElement('div'); element.classList.add('content'); element.innerHTML = box.content; annotation.element.appendChild(element); annotation.line = document.createElement('div'); annotation.line.classList.add('annotation_line'); self.graph.element.appendChild(annotation.line); if ( box.end ) { box.rangeElement = document.createElement('div'); box.rangeElement.classList.add('annotation_range'); self.graph.element.appendChild(box.rangeElement); } } if ( box.end ) { var annotationRangeStart = left; var annotationRangeEnd = Math.min( self.graph.x(box.end), self.graph.x.range()[1] ); // annotation makes more sense at end if ( annotationRangeStart > annotationRangeEnd ) { annotationRangeEnd = left; annotationRangeStart = Math.max( self.graph.x(box.end), self.graph.x.range()[0] ); } var annotationRangeWidth = annotationRangeEnd - annotationRangeStart; box.rangeElement.style.left = annotationRangeStart + 'px'; box.rangeElement.style.width = annotationRangeWidth + 'px'; box.rangeElement.classList.remove('offscreen'); } annotation.line.classList.remove('offscreen'); annotation.line.style.left = left + 'px'; } ); }, this ); }; this.graph.onUpdate( function() { self.update() } ); }; Rickshaw.namespace('Rickshaw.Graph.Axis.Time'); Rickshaw.Graph.Axis.Time = function(args) { var self = this; this.graph = args.graph; this.elements = []; this.ticksTreatment = args.ticksTreatment || 'plain'; this.fixedTimeUnit = args.timeUnit; var time = args.timeFixture || new Rickshaw.Fixtures.Time(); this.appropriateTimeUnit = function() { var unit; var units = time.units; var domain = this.graph.x.domain(); var rangeSeconds = domain[1] - domain[0]; units.forEach( function(u) { if (Math.floor(rangeSeconds / u.seconds) >= 2) { unit = unit || u; } } ); return (unit || time.units[time.units.length - 1]); }; this.tickOffsets = function() { var domain = this.graph.x.domain(); var unit = this.fixedTimeUnit || this.appropriateTimeUnit(); var count = Math.ceil((domain[1] - domain[0]) / unit.seconds); var runningTick = domain[0]; var offsets = []; for (var i = 0; i < count; i++) { var tickValue = time.ceil(runningTick, unit); runningTick = tickValue + unit.seconds / 2; offsets.push( { value: tickValue, unit: unit } ); } return offsets; }; this.render = function() { this.elements.forEach( function(e) { e.parentNode.removeChild(e); } ); this.elements = []; var offsets = this.tickOffsets(); offsets.forEach( function(o) { if (self.graph.x(o.value) > self.graph.x.range()[1]) return; var element = document.createElement('div'); element.style.left = self.graph.x(o.value) + 'px'; element.classList.add('x_tick'); element.classList.add(self.ticksTreatment); var title = document.createElement('div'); title.classList.add('title'); title.innerHTML = o.unit.formatter(new Date(o.value * 1000)); element.appendChild(title); self.graph.element.appendChild(element); self.elements.push(element); } ); }; this.graph.onUpdate( function() { self.render() } ); }; Rickshaw.namespace('Rickshaw.Graph.Axis.X'); Rickshaw.Graph.Axis.X = function(args) { var self = this; var berthRate = 0.10; this.initialize = function(args) { this.graph = args.graph; this.orientation = args.orientation || 'top'; this.color = args.color || "#000000"; this.pixelsPerTick = args.pixelsPerTick || 75; if (args.ticks) this.staticTicks = args.ticks; if (args.tickValues) this.tickValues = args.tickValues; this.tickSize = args.tickSize || 4; this.ticksTreatment = args.ticksTreatment || 'plain'; if (args.element) { this.element = args.element; this._discoverSize(args.element, args); this.vis = d3.select(args.element) .append("svg:svg") .attr('height', this.height) .attr('width', this.width) .attr('stroke', this.color) .attr('class', 'rickshaw_graph x_axis_d3'); this.element = this.vis[0][0]; this.element.style.position = 'relative'; this.setSize({ width: args.width, height: args.height }); } else { this.vis = this.graph.vis; } this.graph.onUpdate( function() { self.render() } ); }; this.setSize = function(args) { args = args || {}; if (!this.element) return; this._discoverSize(this.element.parentNode, args); this.vis .attr('height', this.height) .attr('width', this.width * (1 + berthRate)); var berth = Math.floor(this.width * berthRate / 2); this.element.style.left = -1 * berth + 'px'; }; this.render = function() { if (this._renderWidth !== undefined && this.graph.width !== this._renderWidth) this.setSize({ auto: true }); var axis = d3.svg.axis().scale(this.graph.x).orient(this.orientation); axis.tickFormat( args.tickFormat || function(x) { return x } ); if (this.tickValues) axis.tickValues(this.tickValues); this.ticks = this.staticTicks || Math.floor(this.graph.width / this.pixelsPerTick); var berth = Math.floor(this.width * berthRate / 2) || 0; var bar_offset = this.graph.renderer.name == "bar" && Math.ceil(this.graph.width * 0.95 / this.graph.series[0].data.length / 2) || 0; var transform; if (this.orientation == 'top') { var yOffset = this.height || this.graph.height; transform = 'translate(' + (berth + bar_offset) + ',' + yOffset + ')'; } else { transform = 'translate(' + (berth + bar_offset) + ', 0)'; } if (this.element) { this.vis.selectAll('*').remove(); } this.vis .append("svg:g") .attr("class", ["x_ticks_d3", this.ticksTreatment].join(" ")) .attr("transform", transform) .call(axis.ticks(this.ticks).tickSubdivide(0).tickSize(this.tickSize)); var gridSize = (this.orientation == 'bottom' ? 1 : -1) * this.graph.height; this.graph.vis .append("svg:g") .attr("class", "x_grid_d3") .call(axis.ticks(this.ticks).tickSubdivide(0).tickSize(gridSize)) .selectAll('text') .each(function() { this.parentNode.setAttribute('data-x-value', this.textContent) }); this._renderHeight = this.graph.height; }; this._discoverSize = function(element, args) { if (typeof window !== 'undefined') { var style = window.getComputedStyle(element, null); var elementHeight = parseInt(style.getPropertyValue('height'), 10); if (!args.auto) { var elementWidth = parseInt(style.getPropertyValue('width'), 10); } } this.width = (args.width || elementWidth || this.graph.width) * (1 + berthRate); this.height = args.height || elementHeight || 40; }; this.initialize(args); }; Rickshaw.namespace('Rickshaw.Graph.Axis.Y'); Rickshaw.Graph.Axis.Y = Rickshaw.Class.create( { initialize: function(args) { this.graph = args.graph; this.orientation = args.orientation || 'right'; this.color = args.color || "#000000"; this.pixelsPerTick = args.pixelsPerTick || 75; if (args.ticks) this.staticTicks = args.ticks; if (args.tickValues) this.tickValues = args.tickValues; this.tickSize = args.tickSize || 4; this.ticksTreatment = args.ticksTreatment || 'plain'; this.tickFormat = args.tickFormat || function(y) { return y }; this.berthRate = 0.10; if (args.element) { this.element = args.element; this.vis = d3.select(args.element) .append("svg:svg") .attr('stroke', this.color) .attr('class', 'rickshaw_graph y_axis'); this.element = this.vis[0][0]; this.element.style.position = 'relative'; this.setSize({ width: args.width, height: args.height }); } else { this.vis = this.graph.vis; } var self = this; this.graph.onUpdate( function() { self.render() } ); }, setSize: function(args) { args = args || {}; if (!this.element) return; if (typeof window !== 'undefined') { var style = window.getComputedStyle(this.element.parentNode, null); var elementWidth = parseInt(style.getPropertyValue('width'), 10); if (!args.auto) { var elementHeight = parseInt(style.getPropertyValue('height'), 10); } } this.width = args.width || elementWidth || this.graph.width * this.berthRate; this.height = args.height || elementHeight || this.graph.height; this.vis .attr('width', this.width) .attr('height', this.height * (1 + this.berthRate)); var berth = this.height * this.berthRate; if (this.orientation == 'left') { this.element.style.top = -1 * berth + 'px'; } }, render: function() { if (this._renderHeight !== undefined && this.graph.height !== this._renderHeight) this.setSize({ auto: true }); this.ticks = this.staticTicks || Math.floor(this.graph.height / this.pixelsPerTick); var axis = this._drawAxis(this.graph.y); this._drawGrid(axis); this._renderHeight = this.graph.height; }, _drawAxis: function(scale) { var axis = d3.svg.axis().scale(scale).orient(this.orientation); axis.tickFormat(this.tickFormat); if (this.tickValues) axis.tickValues(this.tickValues); if (this.orientation == 'left') { var berth = this.height * this.berthRate; var transform = 'translate(' + this.width + ', ' + berth + ')'; } if (this.element) { this.vis.selectAll('*').remove(); } this.vis .append("svg:g") .attr("class", ["y_ticks", this.ticksTreatment].join(" ")) .attr("transform", transform) .call(axis.ticks(this.ticks).tickSubdivide(0).tickSize(this.tickSize)); return axis; }, _drawGrid: function(axis) { var gridSize = (this.orientation == 'right' ? 1 : -1) * this.graph.width; this.graph.vis .append("svg:g") .attr("class", "y_grid") .call(axis.ticks(this.ticks).tickSubdivide(0).tickSize(gridSize)) .selectAll('text') .each(function() { this.parentNode.setAttribute('data-y-value', this.textContent) }); } } ); Rickshaw.namespace('Rickshaw.Graph.Axis.Y.Scaled'); Rickshaw.Graph.Axis.Y.Scaled = Rickshaw.Class.create( Rickshaw.Graph.Axis.Y, { initialize: function($super, args) { if (typeof(args.scale) === 'undefined') { throw new Error('Scaled requires scale'); } this.scale = args.scale; if (typeof(args.grid) === 'undefined') { this.grid = true; } else { this.grid = args.grid; } $super(args); }, _drawAxis: function($super, scale) { // Adjust scale's domain to compensate for adjustments to the // renderer's domain (e.g. padding). var domain = this.scale.domain(); var renderDomain = this.graph.renderer.domain().y; var extents = [ Math.min.apply(Math, domain), Math.max.apply(Math, domain)]; // A mapping from the ideal render domain [0, 1] to the extent // of the original scale's domain. This is used to calculate // the extents of the adjusted domain. var extentMap = d3.scale.linear().domain([0, 1]).range(extents); var adjExtents = [ extentMap(renderDomain[0]), extentMap(renderDomain[1])]; // A mapping from the original domain to the adjusted domain. var adjustment = d3.scale.linear().domain(extents).range(adjExtents); // Make a copy of the custom scale, apply the adjusted domain, and // copy the range to match the graph's scale. var adjustedScale = this.scale.copy() .domain(domain.map(adjustment)) .range(scale.range()); return $super(adjustedScale); }, _drawGrid: function($super, axis) { if (this.grid) { // only draw the axis if the grid option is true $super(axis); } } } ); Rickshaw.namespace('Rickshaw.Graph.Behavior.Series.Highlight'); Rickshaw.Graph.Behavior.Series.Highlight = function(args) { this.graph = args.graph; this.legend = args.legend; var self = this; var propertiesSafe = {}; var activeLine = null; var disabledColor = args.disabledColor || function(seriesColor) { return d3.interpolateRgb(seriesColor, d3.rgb('#d8d8d8'))(0.8).toString(); }; var transformFn = args.transform || function(isActive, series) { var newProperties = {}; if (!isActive) { // backwards compability newProperties.color = disabledColor(series.color); } return newProperties; }; this.addHighlightEvents = function (l) { l.element.addEventListener( 'mouseover', function(e) { if (activeLine) return; else activeLine = l; self.legend.lines.forEach( function(line) { var newProperties; var isActive = false; if (l === line) { isActive = true; // if we're not in a stacked renderer bring active line to the top if (self.graph.renderer.unstack && (line.series.renderer ? line.series.renderer.unstack : true)) { var seriesIndex = self.graph.series.indexOf(line.series); line.originalIndex = seriesIndex; var series = self.graph.series.splice(seriesIndex, 1)[0]; self.graph.series.push(series); } } newProperties = transformFn(isActive, line.series); propertiesSafe[line.series.name] = propertiesSafe[line.series.name] || { color : line.series.color, stroke : line.series.stroke }; if (newProperties.color) { line.series.color = newProperties.color; } if (newProperties.stroke) { line.series.stroke = newProperties.stroke; } } ); self.graph.update(); }, false ); l.element.addEventListener( 'mouseout', function(e) { if (!activeLine) return; else activeLine = null; self.legend.lines.forEach( function(line) { // return reordered series to its original place if (l === line && line.hasOwnProperty('originalIndex')) { var series = self.graph.series.pop(); self.graph.series.splice(line.originalIndex, 0, series); delete line.originalIndex; } var lineProperties = propertiesSafe[line.series.name]; if (lineProperties) { line.series.color = lineProperties.color; line.series.stroke = lineProperties.stroke; } } ); self.graph.update(); }, false ); }; if (this.legend) { this.legend.lines.forEach( function(l) { self.addHighlightEvents(l); } ); } }; Rickshaw.namespace('Rickshaw.Graph.Behavior.Series.Order'); Rickshaw.Graph.Behavior.Series.Order = function(args) { this.graph = args.graph; this.legend = args.legend; var self = this; if (typeof window.jQuery == 'undefined') { throw "couldn't find jQuery at window.jQuery"; } if (typeof window.jQuery.ui == 'undefined') { throw "couldn't find jQuery UI at window.jQuery.ui"; } jQuery(function() { jQuery(self.legend.list).sortable( { containment: 'parent', tolerance: 'pointer', update: function( event, ui ) { var series = []; jQuery(self.legend.list).find('li').each( function(index, item) { if (!item.series) return; series.push(item.series); } ); for (var i = self.graph.series.length - 1; i >= 0; i--) { self.graph.series[i] = series.shift(); } self.graph.update(); } } ); jQuery(self.legend.list).disableSelection(); }); //hack to make jquery-ui sortable behave this.graph.onUpdate( function() { var h = window.getComputedStyle(self.legend.element).height; self.legend.element.style.height = h; } ); }; Rickshaw.namespace('Rickshaw.Graph.Behavior.Series.Toggle'); Rickshaw.Graph.Behavior.Series.Toggle = function(args) { this.graph = args.graph; this.legend = args.legend; var self = this; this.addAnchor = function(line) { var anchor = document.createElement('a'); anchor.innerHTML = '&#10004;'; anchor.classList.add('action'); line.element.insertBefore(anchor, line.element.firstChild); anchor.onclick = function(e) { if (line.series.disabled) { line.series.enable(); line.element.classList.remove('disabled'); } else { if (this.graph.series.filter(function(s) { return !s.disabled }).length <= 1) return; line.series.disable(); line.element.classList.add('disabled'); } self.graph.update(); }.bind(this); var label = line.element.getElementsByTagName('span')[0];