rickshaw.js 62 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639
  1. var Rickshaw = {
  2. namespace: function(namespace, obj) {
  3. var parts = namespace.split('.');
  4. var parent = Rickshaw;
  5. for(var i = 1, length = parts.length; i < length; i++) {
  6. currentPart = parts[i];
  7. parent[currentPart] = parent[currentPart] || {};
  8. parent = parent[currentPart];
  9. }
  10. return parent;
  11. },
  12. keys: function(obj) {
  13. var keys = [];
  14. for (var key in obj) keys.push(key);
  15. return keys;
  16. },
  17. extend: function(destination, source) {
  18. for (var property in source) {
  19. destination[property] = source[property];
  20. }
  21. return destination;
  22. }
  23. };
  24. if (typeof module !== 'undefined' && module.exports) {
  25. var d3 = require('d3');
  26. module.exports = Rickshaw;
  27. }
  28. /* Adapted from https://github.com/Jakobo/PTClass */
  29. /*
  30. Copyright (c) 2005-2010 Sam Stephenson
  31. Permission is hereby granted, free of charge, to any person obtaining a copy
  32. of this software and associated documentation files (the "Software"), to deal
  33. in the Software without restriction, including without limitation the rights
  34. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  35. copies of the Software, and to permit persons to whom the Software is
  36. furnished to do so, subject to the following conditions:
  37. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  38. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  39. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  40. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  41. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  42. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  43. SOFTWARE.
  44. */
  45. /* Based on Alex Arnell's inheritance implementation. */
  46. /** section: Language
  47. * class Class
  48. *
  49. * Manages Prototype's class-based OOP system.
  50. *
  51. * Refer to Prototype's web site for a [tutorial on classes and
  52. * inheritance](http://prototypejs.org/learn/class-inheritance).
  53. **/
  54. (function(globalContext) {
  55. /* ------------------------------------ */
  56. /* Import from object.js */
  57. /* ------------------------------------ */
  58. var _toString = Object.prototype.toString,
  59. NULL_TYPE = 'Null',
  60. UNDEFINED_TYPE = 'Undefined',
  61. BOOLEAN_TYPE = 'Boolean',
  62. NUMBER_TYPE = 'Number',
  63. STRING_TYPE = 'String',
  64. OBJECT_TYPE = 'Object',
  65. FUNCTION_CLASS = '[object Function]';
  66. function isFunction(object) {
  67. return _toString.call(object) === FUNCTION_CLASS;
  68. }
  69. function extend(destination, source) {
  70. for (var property in source) if (source.hasOwnProperty(property)) // modify protect primitive slaughter
  71. destination[property] = source[property];
  72. return destination;
  73. }
  74. function keys(object) {
  75. if (Type(object) !== OBJECT_TYPE) { throw new TypeError(); }
  76. var results = [];
  77. for (var property in object) {
  78. if (object.hasOwnProperty(property)) {
  79. results.push(property);
  80. }
  81. }
  82. return results;
  83. }
  84. function Type(o) {
  85. switch(o) {
  86. case null: return NULL_TYPE;
  87. case (void 0): return UNDEFINED_TYPE;
  88. }
  89. var type = typeof o;
  90. switch(type) {
  91. case 'boolean': return BOOLEAN_TYPE;
  92. case 'number': return NUMBER_TYPE;
  93. case 'string': return STRING_TYPE;
  94. }
  95. return OBJECT_TYPE;
  96. }
  97. function isUndefined(object) {
  98. return typeof object === "undefined";
  99. }
  100. /* ------------------------------------ */
  101. /* Import from Function.js */
  102. /* ------------------------------------ */
  103. var slice = Array.prototype.slice;
  104. function argumentNames(fn) {
  105. var names = fn.toString().match(/^[\s\(]*function[^(]*\(([^)]*)\)/)[1]
  106. .replace(/\/\/.*?[\r\n]|\/\*(?:.|[\r\n])*?\*\//g, '')
  107. .replace(/\s+/g, '').split(',');
  108. return names.length == 1 && !names[0] ? [] : names;
  109. }
  110. function wrap(fn, wrapper) {
  111. var __method = fn;
  112. return function() {
  113. var a = update([bind(__method, this)], arguments);
  114. return wrapper.apply(this, a);
  115. }
  116. }
  117. function update(array, args) {
  118. var arrayLength = array.length, length = args.length;
  119. while (length--) array[arrayLength + length] = args[length];
  120. return array;
  121. }
  122. function merge(array, args) {
  123. array = slice.call(array, 0);
  124. return update(array, args);
  125. }
  126. function bind(fn, context) {
  127. if (arguments.length < 2 && isUndefined(arguments[0])) return this;
  128. var __method = fn, args = slice.call(arguments, 2);
  129. return function() {
  130. var a = merge(args, arguments);
  131. return __method.apply(context, a);
  132. }
  133. }
  134. /* ------------------------------------ */
  135. /* Import from Prototype.js */
  136. /* ------------------------------------ */
  137. var emptyFunction = function(){};
  138. var Class = (function() {
  139. // Some versions of JScript fail to enumerate over properties, names of which
  140. // correspond to non-enumerable properties in the prototype chain
  141. var IS_DONTENUM_BUGGY = (function(){
  142. for (var p in { toString: 1 }) {
  143. // check actual property name, so that it works with augmented Object.prototype
  144. if (p === 'toString') return false;
  145. }
  146. return true;
  147. })();
  148. function subclass() {};
  149. function create() {
  150. var parent = null, properties = [].slice.apply(arguments);
  151. if (isFunction(properties[0]))
  152. parent = properties.shift();
  153. function klass() {
  154. this.initialize.apply(this, arguments);
  155. }
  156. extend(klass, Class.Methods);
  157. klass.superclass = parent;
  158. klass.subclasses = [];
  159. if (parent) {
  160. subclass.prototype = parent.prototype;
  161. klass.prototype = new subclass;
  162. try { parent.subclasses.push(klass) } catch(e) {}
  163. }
  164. for (var i = 0, length = properties.length; i < length; i++)
  165. klass.addMethods(properties[i]);
  166. if (!klass.prototype.initialize)
  167. klass.prototype.initialize = emptyFunction;
  168. klass.prototype.constructor = klass;
  169. return klass;
  170. }
  171. function addMethods(source) {
  172. var ancestor = this.superclass && this.superclass.prototype,
  173. properties = keys(source);
  174. // IE6 doesn't enumerate `toString` and `valueOf` (among other built-in `Object.prototype`) properties,
  175. // Force copy if they're not Object.prototype ones.
  176. // Do not copy other Object.prototype.* for performance reasons
  177. if (IS_DONTENUM_BUGGY) {
  178. if (source.toString != Object.prototype.toString)
  179. properties.push("toString");
  180. if (source.valueOf != Object.prototype.valueOf)
  181. properties.push("valueOf");
  182. }
  183. for (var i = 0, length = properties.length; i < length; i++) {
  184. var property = properties[i], value = source[property];
  185. if (ancestor && isFunction(value) &&
  186. argumentNames(value)[0] == "$super") {
  187. var method = value;
  188. value = wrap((function(m) {
  189. return function() { return ancestor[m].apply(this, arguments); };
  190. })(property), method);
  191. value.valueOf = bind(method.valueOf, method);
  192. value.toString = bind(method.toString, method);
  193. }
  194. this.prototype[property] = value;
  195. }
  196. return this;
  197. }
  198. return {
  199. create: create,
  200. Methods: {
  201. addMethods: addMethods
  202. }
  203. };
  204. })();
  205. if (globalContext.exports) {
  206. globalContext.exports.Class = Class;
  207. }
  208. else {
  209. globalContext.Class = Class;
  210. }
  211. })(Rickshaw);
  212. Rickshaw.namespace('Rickshaw.Compat.ClassList');
  213. Rickshaw.Compat.ClassList = function() {
  214. /* adapted from http://purl.eligrey.com/github/classList.js/blob/master/classList.js */
  215. if (typeof document !== "undefined" && !("classList" in document.createElement("a"))) {
  216. (function (view) {
  217. "use strict";
  218. var
  219. classListProp = "classList"
  220. , protoProp = "prototype"
  221. , elemCtrProto = (view.HTMLElement || view.Element)[protoProp]
  222. , objCtr = Object
  223. , strTrim = String[protoProp].trim || function () {
  224. return this.replace(/^\s+|\s+$/g, "");
  225. }
  226. , arrIndexOf = Array[protoProp].indexOf || function (item) {
  227. var
  228. i = 0
  229. , len = this.length
  230. ;
  231. for (; i < len; i++) {
  232. if (i in this && this[i] === item) {
  233. return i;
  234. }
  235. }
  236. return -1;
  237. }
  238. // Vendors: please allow content code to instantiate DOMExceptions
  239. , DOMEx = function (type, message) {
  240. this.name = type;
  241. this.code = DOMException[type];
  242. this.message = message;
  243. }
  244. , checkTokenAndGetIndex = function (classList, token) {
  245. if (token === "") {
  246. throw new DOMEx(
  247. "SYNTAX_ERR"
  248. , "An invalid or illegal string was specified"
  249. );
  250. }
  251. if (/\s/.test(token)) {
  252. throw new DOMEx(
  253. "INVALID_CHARACTER_ERR"
  254. , "String contains an invalid character"
  255. );
  256. }
  257. return arrIndexOf.call(classList, token);
  258. }
  259. , ClassList = function (elem) {
  260. var
  261. trimmedClasses = strTrim.call(elem.className)
  262. , classes = trimmedClasses ? trimmedClasses.split(/\s+/) : []
  263. , i = 0
  264. , len = classes.length
  265. ;
  266. for (; i < len; i++) {
  267. this.push(classes[i]);
  268. }
  269. this._updateClassName = function () {
  270. elem.className = this.toString();
  271. };
  272. }
  273. , classListProto = ClassList[protoProp] = []
  274. , classListGetter = function () {
  275. return new ClassList(this);
  276. }
  277. ;
  278. // Most DOMException implementations don't allow calling DOMException's toString()
  279. // on non-DOMExceptions. Error's toString() is sufficient here.
  280. DOMEx[protoProp] = Error[protoProp];
  281. classListProto.item = function (i) {
  282. return this[i] || null;
  283. };
  284. classListProto.contains = function (token) {
  285. token += "";
  286. return checkTokenAndGetIndex(this, token) !== -1;
  287. };
  288. classListProto.add = function (token) {
  289. token += "";
  290. if (checkTokenAndGetIndex(this, token) === -1) {
  291. this.push(token);
  292. this._updateClassName();
  293. }
  294. };
  295. classListProto.remove = function (token) {
  296. token += "";
  297. var index = checkTokenAndGetIndex(this, token);
  298. if (index !== -1) {
  299. this.splice(index, 1);
  300. this._updateClassName();
  301. }
  302. };
  303. classListProto.toggle = function (token) {
  304. token += "";
  305. if (checkTokenAndGetIndex(this, token) === -1) {
  306. this.add(token);
  307. } else {
  308. this.remove(token);
  309. }
  310. };
  311. classListProto.toString = function () {
  312. return this.join(" ");
  313. };
  314. if (objCtr.defineProperty) {
  315. var classListPropDesc = {
  316. get: classListGetter
  317. , enumerable: true
  318. , configurable: true
  319. };
  320. try {
  321. objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc);
  322. } catch (ex) { // IE 8 doesn't support enumerable:true
  323. if (ex.number === -0x7FF5EC54) {
  324. classListPropDesc.enumerable = false;
  325. objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc);
  326. }
  327. }
  328. } else if (objCtr[protoProp].__defineGetter__) {
  329. elemCtrProto.__defineGetter__(classListProp, classListGetter);
  330. }
  331. }(window));
  332. }
  333. };
  334. if ( (typeof RICKSHAW_NO_COMPAT !== "undefined" && !RICKSHAW_NO_COMPAT) || typeof RICKSHAW_NO_COMPAT === "undefined") {
  335. new Rickshaw.Compat.ClassList();
  336. }
  337. Rickshaw.namespace('Rickshaw.Graph');
  338. Rickshaw.Graph = function(args) {
  339. this.element = args.element;
  340. this.series = args.series;
  341. this.defaults = {
  342. interpolation: 'cardinal',
  343. offset: 'zero',
  344. min: undefined,
  345. max: undefined
  346. };
  347. Rickshaw.keys(this.defaults).forEach( function(k) {
  348. this[k] = args[k] || this.defaults[k];
  349. }, this );
  350. this.window = {};
  351. this.updateCallbacks = [];
  352. var self = this;
  353. this.initialize = function(args) {
  354. this.validateSeries(args.series);
  355. this.series.active = function() { return self.series.filter( function(s) { return !s.disabled } ) };
  356. this.setSize({ width: args.width, height: args.height });
  357. this.element.classList.add('rickshaw_graph');
  358. this.vis = d3.select(this.element)
  359. .append("svg:svg")
  360. .attr('width', this.width)
  361. .attr('height', this.height);
  362. var renderers = [
  363. Rickshaw.Graph.Renderer.Stack,
  364. Rickshaw.Graph.Renderer.Line,
  365. Rickshaw.Graph.Renderer.Bar,
  366. Rickshaw.Graph.Renderer.Area,
  367. Rickshaw.Graph.Renderer.ScatterPlot
  368. ];
  369. renderers.forEach( function(r) {
  370. if (!r) return;
  371. self.registerRenderer(new r( { graph: self } ));
  372. } );
  373. this.setRenderer(args.renderer || 'stack', args);
  374. this.discoverRange();
  375. };
  376. this.validateSeries = function(series) {
  377. if (!(series instanceof Array) && !(series instanceof Rickshaw.Series)) {
  378. var seriesSignature = Object.prototype.toString.apply(series);
  379. throw "series is not an array: " + seriesSignature;
  380. }
  381. var pointsCount;
  382. series.forEach( function(s) {
  383. if (!(s instanceof Object)) {
  384. throw "series element is not an object: " + s;
  385. }
  386. if (!(s.data)) {
  387. throw "series has no data: " + JSON.stringify(s);
  388. }
  389. if (!(s.data instanceof Array)) {
  390. throw "series data is not an array: " + JSON.stringify(s.data);
  391. }
  392. pointsCount = pointsCount || s.data.length;
  393. if (pointsCount && s.data.length != pointsCount) {
  394. throw "series cannot have differing numbers of points: " +
  395. pointsCount + " vs " + s.data.length + "; see Rickshaw.Series.zeroFill()";
  396. }
  397. var dataTypeX = typeof s.data[0].x;
  398. var dataTypeY = typeof s.data[0].y;
  399. if (dataTypeX != 'number' || dataTypeY != 'number') {
  400. throw "x and y properties of points should be numbers instead of " +
  401. dataTypeX + " and " + dataTypeY;
  402. }
  403. } );
  404. };
  405. this.dataDomain = function() {
  406. // take from the first series
  407. var data = this.series[0].data;
  408. return [ data[0].x, data.slice(-1).shift().x ];
  409. };
  410. this.discoverRange = function() {
  411. var domain = this.renderer.domain();
  412. this.x = d3.scale.linear().domain(domain.x).range([0, this.width]);
  413. this.y = d3.scale.linear().domain(domain.y).range([this.height, 0]);
  414. this.y.magnitude = d3.scale.linear()
  415. .domain([domain.y[0] - domain.y[0], domain.y[1] - domain.y[0]])
  416. .range([0, this.height]);
  417. };
  418. this.render = function() {
  419. var stackedData = this.stackData();
  420. this.discoverRange();
  421. this.renderer.render();
  422. this.updateCallbacks.forEach( function(callback) {
  423. callback();
  424. } );
  425. };
  426. this.update = this.render;
  427. this.stackData = function() {
  428. var data = this.series.active()
  429. .map( function(d) { return d.data } )
  430. .map( function(d) { return d.filter( function(d) { return this._slice(d) }, this ) }, this);
  431. this.stackData.hooks.data.forEach( function(entry) {
  432. data = entry.f.apply(self, [data]);
  433. } );
  434. var layout = d3.layout.stack();
  435. layout.offset( self.offset );
  436. var stackedData = layout(data);
  437. this.stackData.hooks.after.forEach( function(entry) {
  438. stackedData = entry.f.apply(self, [data]);
  439. } );
  440. var i = 0;
  441. this.series.forEach( function(series) {
  442. if (series.disabled) return;
  443. series.stack = stackedData[i++];
  444. } );
  445. this.stackedData = stackedData;
  446. return stackedData;
  447. };
  448. this.stackData.hooks = { data: [], after: [] };
  449. this._slice = function(d) {
  450. if (this.window.xMin || this.window.xMax) {
  451. var isInRange = true;
  452. if (this.window.xMin && d.x < this.window.xMin) isInRange = false;
  453. if (this.window.xMax && d.x > this.window.xMax) isInRange = false;
  454. return isInRange;
  455. }
  456. return true;
  457. };
  458. this.onUpdate = function(callback) {
  459. this.updateCallbacks.push(callback);
  460. };
  461. this.registerRenderer = function(renderer) {
  462. this._renderers = this._renderers || {};
  463. this._renderers[renderer.name] = renderer;
  464. };
  465. this.configure = function(args) {
  466. if (args.width || args.height) {
  467. this.setSize(args);
  468. }
  469. Rickshaw.keys(this.defaults).forEach( function(k) {
  470. this[k] = k in args ? args[k]
  471. : k in this ? this[k]
  472. : this.defaults[k];
  473. }, this );
  474. this.setRenderer(args.renderer || this.renderer.name, args);
  475. };
  476. this.setRenderer = function(name, args) {
  477. if (!this._renderers[name]) {
  478. throw "couldn't find renderer " + name;
  479. }
  480. this.renderer = this._renderers[name];
  481. if (typeof args == 'object') {
  482. this.renderer.configure(args);
  483. }
  484. };
  485. this.setSize = function(args) {
  486. args = args || {};
  487. if (typeof window !== undefined) {
  488. var style = window.getComputedStyle(this.element, null);
  489. var elementWidth = parseInt(style.getPropertyValue('width'));
  490. var elementHeight = parseInt(style.getPropertyValue('height'));
  491. }
  492. this.width = args.width || elementWidth || 400;
  493. this.height = args.height || elementHeight || 250;
  494. this.vis && this.vis
  495. .attr('width', this.width)
  496. .attr('height', this.height);
  497. }
  498. this.initialize(args);
  499. };
  500. Rickshaw.namespace('Rickshaw.Fixtures.Color');
  501. Rickshaw.Fixtures.Color = function() {
  502. this.schemes = {};
  503. this.schemes.spectrum14 = [
  504. '#ecb796',
  505. '#dc8f70',
  506. '#b2a470',
  507. '#92875a',
  508. '#716c49',
  509. '#d2ed82',
  510. '#bbe468',
  511. '#a1d05d',
  512. '#e7cbe6',
  513. '#d8aad6',
  514. '#a888c2',
  515. '#9dc2d3',
  516. '#649eb9',
  517. '#387aa3'
  518. ].reverse();
  519. this.schemes.spectrum2000 = [
  520. '#57306f',
  521. '#514c76',
  522. '#646583',
  523. '#738394',
  524. '#6b9c7d',
  525. '#84b665',
  526. '#a7ca50',
  527. '#bfe746',
  528. '#e2f528',
  529. '#fff726',
  530. '#ecdd00',
  531. '#d4b11d',
  532. '#de8800',
  533. '#de4800',
  534. '#c91515',
  535. '#9a0000',
  536. '#7b0429',
  537. '#580839',
  538. '#31082b'
  539. ];
  540. this.schemes.spectrum2001 = [
  541. '#2f243f',
  542. '#3c2c55',
  543. '#4a3768',
  544. '#565270',
  545. '#6b6b7c',
  546. '#72957f',
  547. '#86ad6e',
  548. '#a1bc5e',
  549. '#b8d954',
  550. '#d3e04e',
  551. '#ccad2a',
  552. '#cc8412',
  553. '#c1521d',
  554. '#ad3821',
  555. '#8a1010',
  556. '#681717',
  557. '#531e1e',
  558. '#3d1818',
  559. '#320a1b'
  560. ];
  561. this.schemes.classic9 = [
  562. '#423d4f',
  563. '#4a6860',
  564. '#848f39',
  565. '#a2b73c',
  566. '#ddcb53',
  567. '#c5a32f',
  568. '#7d5836',
  569. '#963b20',
  570. '#7c2626',
  571. '#491d37',
  572. '#2f254a'
  573. ].reverse();
  574. this.schemes.httpStatus = {
  575. 503: '#ea5029',
  576. 502: '#d23f14',
  577. 500: '#bf3613',
  578. 410: '#efacea',
  579. 409: '#e291dc',
  580. 403: '#f457e8',
  581. 408: '#e121d2',
  582. 401: '#b92dae',
  583. 405: '#f47ceb',
  584. 404: '#a82a9f',
  585. 400: '#b263c6',
  586. 301: '#6fa024',
  587. 302: '#87c32b',
  588. 307: '#a0d84c',
  589. 304: '#28b55c',
  590. 200: '#1a4f74',
  591. 206: '#27839f',
  592. 201: '#52adc9',
  593. 202: '#7c979f',
  594. 203: '#a5b8bd',
  595. 204: '#c1cdd1'
  596. };
  597. this.schemes.colorwheel = [
  598. '#b5b6a9',
  599. '#858772',
  600. '#785f43',
  601. '#96557e',
  602. '#4682b4',
  603. '#65b9ac',
  604. '#73c03a',
  605. '#cb513a'
  606. ].reverse();
  607. this.schemes.cool = [
  608. '#5e9d2f',
  609. '#73c03a',
  610. '#4682b4',
  611. '#7bc3b8',
  612. '#a9884e',
  613. '#c1b266',
  614. '#a47493',
  615. '#c09fb5'
  616. ];
  617. this.schemes.munin = [
  618. '#00cc00',
  619. '#0066b3',
  620. '#ff8000',
  621. '#ffcc00',
  622. '#330099',
  623. '#990099',
  624. '#ccff00',
  625. '#ff0000',
  626. '#808080',
  627. '#008f00',
  628. '#00487d',
  629. '#b35a00',
  630. '#b38f00',
  631. '#6b006b',
  632. '#8fb300',
  633. '#b30000',
  634. '#bebebe',
  635. '#80ff80',
  636. '#80c9ff',
  637. '#ffc080',
  638. '#ffe680',
  639. '#aa80ff',
  640. '#ee00cc',
  641. '#ff8080',
  642. '#666600',
  643. '#ffbfff',
  644. '#00ffcc',
  645. '#cc6699',
  646. '#999900'
  647. ];
  648. };
  649. Rickshaw.namespace('Rickshaw.Fixtures.RandomData');
  650. Rickshaw.Fixtures.RandomData = function(timeInterval) {
  651. var addData;
  652. timeInterval = timeInterval || 1;
  653. var lastRandomValue = 200;
  654. var timeBase = Math.floor(new Date().getTime() / 1000);
  655. this.addData = function(data) {
  656. var randomValue = Math.random() * 100 + 15 + lastRandomValue;
  657. var index = data[0].length;
  658. var counter = 1;
  659. data.forEach( function(series) {
  660. var randomVariance = Math.random() * 20;
  661. var v = randomValue / 25 + counter++
  662. + (Math.cos((index * counter * 11) / 960) + 2) * 15
  663. + (Math.cos(index / 7) + 2) * 7
  664. + (Math.cos(index / 17) + 2) * 1;
  665. series.push( { x: (index * timeInterval) + timeBase, y: v + randomVariance } );
  666. } );
  667. lastRandomValue = randomValue * .85;
  668. }
  669. };
  670. Rickshaw.namespace('Rickshaw.Fixtures.Time');
  671. Rickshaw.Fixtures.Time = function() {
  672. var tzOffset = new Date().getTimezoneOffset() * 60;
  673. var self = this;
  674. this.months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
  675. this.units = [
  676. {
  677. name: 'decade',
  678. seconds: 86400 * 365.25 * 10,
  679. formatter: function(d) { return (parseInt(d.getUTCFullYear() / 10) * 10) }
  680. }, {
  681. name: 'year',
  682. seconds: 86400 * 365.25,
  683. formatter: function(d) { return d.getUTCFullYear() }
  684. }, {
  685. name: 'month',
  686. seconds: 86400 * 30.5,
  687. formatter: function(d) { return self.months[d.getUTCMonth()] }
  688. }, {
  689. name: 'week',
  690. seconds: 86400 * 7,
  691. formatter: function(d) { return self.formatDate(d) }
  692. }, {
  693. name: 'day',
  694. seconds: 86400,
  695. formatter: function(d) { return d.getUTCDate() }
  696. }, {
  697. name: '6 hour',
  698. seconds: 3600 * 6,
  699. formatter: function(d) { return self.formatTime(d) }
  700. }, {
  701. name: 'hour',
  702. seconds: 3600,
  703. formatter: function(d) { return self.formatTime(d) }
  704. }, {
  705. name: '15 minute',
  706. seconds: 60 * 15,
  707. formatter: function(d) { return self.formatTime(d) }
  708. }, {
  709. name: 'minute',
  710. seconds: 60,
  711. formatter: function(d) { return d.getUTCMinutes() }
  712. }, {
  713. name: '15 second',
  714. seconds: 15,
  715. formatter: function(d) { return d.getUTCSeconds() + 's' }
  716. }, {
  717. name: 'second',
  718. seconds: 1,
  719. formatter: function(d) { return d.getUTCSeconds() + 's' }
  720. }
  721. ];
  722. this.unit = function(unitName) {
  723. return this.units.filter( function(unit) { return unitName == unit.name } ).shift();
  724. };
  725. this.formatDate = function(d) {
  726. return d.toUTCString().match(/, (\w+ \w+ \w+)/)[1];
  727. };
  728. this.formatTime = function(d) {
  729. return d.toUTCString().match(/(\d+:\d+):/)[1];
  730. };
  731. this.ceil = function(time, unit) {
  732. if (unit.name == 'month') {
  733. var nearFuture = new Date((time + unit.seconds - 1) * 1000);
  734. var rounded = new Date(0);
  735. rounded.setUTCFullYear(nearFuture.getUTCFullYear());
  736. rounded.setUTCMonth(nearFuture.getUTCMonth());
  737. rounded.setUTCDate(1);
  738. rounded.setUTCHours(0);
  739. rounded.setUTCMinutes(0);
  740. rounded.setUTCSeconds(0);
  741. rounded.setUTCMilliseconds(0);
  742. return rounded.getTime() / 1000;
  743. }
  744. if (unit.name == 'year') {
  745. var nearFuture = new Date((time + unit.seconds - 1) * 1000);
  746. var rounded = new Date(0);
  747. rounded.setUTCFullYear(nearFuture.getUTCFullYear());
  748. rounded.setUTCMonth(0);
  749. rounded.setUTCDate(1);
  750. rounded.setUTCHours(0);
  751. rounded.setUTCMinutes(0);
  752. rounded.setUTCSeconds(0);
  753. rounded.setUTCMilliseconds(0);
  754. return rounded.getTime() / 1000;
  755. }
  756. return Math.ceil(time / unit.seconds) * unit.seconds;
  757. };
  758. };
  759. Rickshaw.namespace('Rickshaw.Fixtures.Number');
  760. Rickshaw.Fixtures.Number.formatKMBT = function(y) {
  761. if (y >= 1000000000000) { return y / 1000000000000 + "T" }
  762. else if (y >= 1000000000) { return y / 1000000000 + "B" }
  763. else if (y >= 1000000) { return y / 1000000 + "M" }
  764. else if (y >= 1000) { return y / 1000 + "K" }
  765. else if (y < 1 && y > 0) { return y.toFixed(2) }
  766. else if (y == 0) { return '' }
  767. else { return y }
  768. };
  769. Rickshaw.Fixtures.Number.formatBase1024KMGTP = function(y) {
  770. if (y >= 1125899906842624) { return y / 1125899906842624 + "P" }
  771. else if (y >= 1099511627776){ return y / 1099511627776 + "T" }
  772. else if (y >= 1073741824) { return y / 1073741824 + "G" }
  773. else if (y >= 1048576) { return y / 1048576 + "M" }
  774. else if (y >= 1024) { return y / 1024 + "K" }
  775. else if (y < 1 && y > 0) { return y.toFixed(2) }
  776. else if (y == 0) { return '' }
  777. else { return y }
  778. };
  779. Rickshaw.namespace("Rickshaw.Color.Palette");
  780. Rickshaw.Color.Palette = function(args) {
  781. var color = new Rickshaw.Fixtures.Color();
  782. args = args || {};
  783. this.schemes = {};
  784. this.scheme = color.schemes[args.scheme] || args.scheme || color.schemes.colorwheel;
  785. this.runningIndex = 0;
  786. this.generatorIndex = 0;
  787. if (args.interpolatedStopCount) {
  788. var schemeCount = this.scheme.length - 1;
  789. var i, j, scheme = [];
  790. for (i = 0; i < schemeCount; i++) {
  791. scheme.push(this.scheme[i]);
  792. var generator = d3.interpolateHsl(this.scheme[i], this.scheme[i + 1]);
  793. for (j = 1; j < args.interpolatedStopCount; j++) {
  794. scheme.push(generator((1 / args.interpolatedStopCount) * j));
  795. }
  796. }
  797. scheme.push(this.scheme[this.scheme.length - 1]);
  798. this.scheme = scheme;
  799. }
  800. this.rotateCount = this.scheme.length;
  801. this.color = function(key) {
  802. return this.scheme[key] || this.scheme[this.runningIndex++] || this.interpolateColor() || '#808080';
  803. };
  804. this.interpolateColor = function() {
  805. if (!Array.isArray(this.scheme)) return;
  806. var color;
  807. if (this.generatorIndex == this.rotateCount * 2 - 1) {
  808. color = d3.interpolateHsl(this.scheme[this.generatorIndex], this.scheme[0])(0.5);
  809. this.generatorIndex = 0;
  810. this.rotateCount *= 2;
  811. } else {
  812. color = d3.interpolateHsl(this.scheme[this.generatorIndex], this.scheme[this.generatorIndex + 1])(0.5);
  813. this.generatorIndex++;
  814. }
  815. this.scheme.push(color);
  816. return color;
  817. };
  818. };
  819. Rickshaw.namespace('Rickshaw.Graph.Ajax');
  820. Rickshaw.Graph.Ajax = Rickshaw.Class.create( {
  821. initialize: function(args) {
  822. this.dataURL = args.dataURL;
  823. this.onData = args.onData || function(d) { return d };
  824. this.onComplete = args.onComplete || function() {};
  825. this.onError = args.onError || function() {};
  826. this.args = args; // pass through to Rickshaw.Graph
  827. this.request();
  828. },
  829. request: function() {
  830. $.ajax( {
  831. url: this.dataURL,
  832. dataType: 'json',
  833. success: this.success.bind(this),
  834. error: this.error.bind(this)
  835. } );
  836. },
  837. error: function() {
  838. console.log("error loading dataURL: " + this.dataURL);
  839. this.onError(this);
  840. },
  841. success: function(data, status) {
  842. data = this.onData(data);
  843. this.args.series = this._splice({ data: data, series: this.args.series });
  844. this.graph = new Rickshaw.Graph(this.args);
  845. this.graph.render();
  846. this.onComplete(this);
  847. },
  848. _splice: function(args) {
  849. var data = args.data;
  850. var series = args.series;
  851. if (!args.series) return data;
  852. series.forEach( function(s) {
  853. var seriesKey = s.key || s.name;
  854. if (!seriesKey) throw "series needs a key or a name";
  855. data.forEach( function(d) {
  856. var dataKey = d.key || d.name;
  857. if (!dataKey) throw "data needs a key or a name";
  858. if (seriesKey == dataKey) {
  859. var properties = ['color', 'name', 'data'];
  860. properties.forEach( function(p) {
  861. s[p] = s[p] || d[p];
  862. } );
  863. }
  864. } );
  865. } );
  866. return series;
  867. }
  868. } );
  869. Rickshaw.namespace('Rickshaw.Graph.Annotate');
  870. Rickshaw.Graph.Annotate = function(args) {
  871. var graph = this.graph = args.graph;
  872. this.elements = { timeline: args.element };
  873. var self = this;
  874. this.data = {};
  875. this.elements.timeline.classList.add('rickshaw_annotation_timeline');
  876. this.add = function(time, content, end_time) {
  877. self.data[time] = self.data[time] || {'boxes': []};
  878. self.data[time].boxes.push({content: content, end: end_time});
  879. };
  880. this.update = function() {
  881. Rickshaw.keys(self.data).forEach( function(time) {
  882. var annotation = self.data[time];
  883. var left = self.graph.x(time);
  884. if (left < 0 || left > self.graph.x.range()[1]) {
  885. if (annotation.element) {
  886. annotation.line.classList.add('offscreen');
  887. annotation.element.style.display = 'none';
  888. }
  889. annotation.boxes.forEach( function(box) {
  890. if ( box.rangeElement ) box.rangeElement.classList.add('offscreen');
  891. });
  892. return;
  893. }
  894. if (!annotation.element) {
  895. var element = annotation.element = document.createElement('div');
  896. element.classList.add('annotation');
  897. this.elements.timeline.appendChild(element);
  898. element.addEventListener('click', function(e) {
  899. element.classList.toggle('active');
  900. annotation.line.classList.toggle('active');
  901. annotation.boxes.forEach( function(box) {
  902. if ( box.rangeElement ) box.rangeElement.classList.toggle('active');
  903. });
  904. }, false);
  905. }
  906. annotation.element.style.left = left + 'px';
  907. annotation.element.style.display = 'block';
  908. annotation.boxes.forEach( function(box) {
  909. var element = box.element;
  910. if (!element) {
  911. element = box.element = document.createElement('div');
  912. element.classList.add('content');
  913. element.innerHTML = box.content;
  914. annotation.element.appendChild(element);
  915. annotation.line = document.createElement('div');
  916. annotation.line.classList.add('annotation_line');
  917. self.graph.element.appendChild(annotation.line);
  918. if ( box.end ) {
  919. box.rangeElement = document.createElement('div');
  920. box.rangeElement.classList.add('annotation_range');
  921. self.graph.element.appendChild(box.rangeElement);
  922. }
  923. }
  924. if ( box.end ) {
  925. var annotationRangeStart = left;
  926. var annotationRangeEnd = Math.min( self.graph.x(box.end), self.graph.x.range()[1] );
  927. // annotation makes more sense at end
  928. if ( annotationRangeStart > annotationRangeEnd ) {
  929. annotationRangeEnd = left;
  930. annotationRangeStart = Math.max( self.graph.x(box.end), self.graph.x.range()[0] );
  931. }
  932. var annotationRangeWidth = annotationRangeEnd - annotationRangeStart;
  933. box.rangeElement.style.left = annotationRangeStart + 'px';
  934. box.rangeElement.style.width = annotationRangeWidth + 'px'
  935. box.rangeElement.classList.remove('offscreen');
  936. }
  937. annotation.line.classList.remove('offscreen');
  938. annotation.line.style.left = left + 'px';
  939. } );
  940. }, this );
  941. };
  942. this.graph.onUpdate( function() { self.update() } );
  943. };
  944. Rickshaw.namespace('Rickshaw.Graph.Axis.Time');
  945. Rickshaw.Graph.Axis.Time = function(args) {
  946. var self = this;
  947. this.graph = args.graph;
  948. this.elements = [];
  949. this.ticksTreatment = args.ticksTreatment || 'plain';
  950. this.fixedTimeUnit = args.timeUnit;
  951. var time = new Rickshaw.Fixtures.Time();
  952. this.appropriateTimeUnit = function() {
  953. var unit;
  954. var units = time.units;
  955. var domain = this.graph.x.domain();
  956. var rangeSeconds = domain[1] - domain[0];
  957. units.forEach( function(u) {
  958. if (Math.floor(rangeSeconds / u.seconds) >= 2) {
  959. unit = unit || u;
  960. }
  961. } );
  962. return (unit || time.units[time.units.length - 1]);
  963. };
  964. this.tickOffsets = function() {
  965. var domain = this.graph.x.domain();
  966. var unit = this.fixedTimeUnit || this.appropriateTimeUnit();
  967. var count = Math.ceil((domain[1] - domain[0]) / unit.seconds);
  968. var runningTick = domain[0];
  969. var offsets = [];
  970. for (var i = 0; i < count; i++) {
  971. tickValue = time.ceil(runningTick, unit);
  972. runningTick = tickValue + unit.seconds / 2;
  973. offsets.push( { value: tickValue, unit: unit } );
  974. }
  975. return offsets;
  976. };
  977. this.render = function() {
  978. this.elements.forEach( function(e) {
  979. e.parentNode.removeChild(e);
  980. } );
  981. this.elements = [];
  982. var offsets = this.tickOffsets();
  983. offsets.forEach( function(o) {
  984. if (self.graph.x(o.value) > self.graph.x.range()[1]) return;
  985. var element = document.createElement('div');
  986. element.style.left = self.graph.x(o.value) + 'px';
  987. element.classList.add('x_tick');
  988. element.classList.add(self.ticksTreatment);
  989. var title = document.createElement('div');
  990. title.classList.add('title');
  991. title.innerHTML = o.unit.formatter(new Date(o.value * 1000));
  992. element.appendChild(title);
  993. self.graph.element.appendChild(element);
  994. self.elements.push(element);
  995. } );
  996. };
  997. this.graph.onUpdate( function() { self.render() } );
  998. };
  999. Rickshaw.namespace('Rickshaw.Graph.Axis.Y');
  1000. Rickshaw.Graph.Axis.Y = function(args) {
  1001. var self = this;
  1002. var berthRate = 0.10;
  1003. this.initialize = function(args) {
  1004. this.graph = args.graph;
  1005. this.orientation = args.orientation || 'right';
  1006. var pixelsPerTick = args.pixelsPerTick || 75;
  1007. this.ticks = args.ticks || Math.floor(this.graph.height / pixelsPerTick);
  1008. this.tickSize = args.tickSize || 4;
  1009. this.ticksTreatment = args.ticksTreatment || 'plain';
  1010. if (args.element) {
  1011. this.element = args.element;
  1012. this.vis = d3.select(args.element)
  1013. .append("svg:svg")
  1014. .attr('class', 'rickshaw_graph y_axis');
  1015. this.element = this.vis[0][0];
  1016. this.element.style.position = 'relative';
  1017. this.setSize({ width: args.width, height: args.height });
  1018. } else {
  1019. this.vis = this.graph.vis;
  1020. }
  1021. this.graph.onUpdate( function() { self.render() } );
  1022. };
  1023. this.setSize = function(args) {
  1024. args = args || {};
  1025. if (!this.element) return;
  1026. if (typeof window !== 'undefined') {
  1027. var style = window.getComputedStyle(this.element.parentNode, null);
  1028. var elementWidth = parseInt(style.getPropertyValue('width'));
  1029. if (!args.auto) {
  1030. var elementHeight = parseInt(style.getPropertyValue('height'));
  1031. }
  1032. }
  1033. this.width = args.width || elementWidth || this.graph.width * berthRate;
  1034. this.height = args.height || elementHeight || this.graph.height;
  1035. this.vis
  1036. .attr('width', this.width)
  1037. .attr('height', this.height * (1 + berthRate));
  1038. var berth = this.height * berthRate;
  1039. this.element.style.top = -1 * berth + 'px';
  1040. };
  1041. this.render = function() {
  1042. if (this.graph.height !== this._renderHeight) this.setSize({ auto: true });
  1043. var axis = d3.svg.axis().scale(this.graph.y).orient(this.orientation);
  1044. axis.tickFormat( args.tickFormat || function(y) { return y } );
  1045. if (this.orientation == 'left') {
  1046. var berth = this.height * berthRate;
  1047. var transform = 'translate(' + this.width + ', ' + berth + ')';
  1048. }
  1049. if (this.element) {
  1050. this.vis.selectAll('*').remove();
  1051. }
  1052. this.vis
  1053. .append("svg:g")
  1054. .attr("class", ["y_ticks", this.ticksTreatment].join(" "))
  1055. .attr("transform", transform)
  1056. .call(axis.ticks(this.ticks).tickSubdivide(0).tickSize(this.tickSize))
  1057. var gridSize = (this.orientation == 'right' ? 1 : -1) * this.graph.width;
  1058. this.graph.vis
  1059. .append("svg:g")
  1060. .attr("class", "y_grid")
  1061. .call(axis.ticks(this.ticks).tickSubdivide(0).tickSize(gridSize));
  1062. this._renderHeight = this.graph.height;
  1063. };
  1064. this.initialize(args);
  1065. };
  1066. Rickshaw.namespace('Rickshaw.Graph.Behavior.Series.Highlight');
  1067. Rickshaw.Graph.Behavior.Series.Highlight = function(args) {
  1068. this.graph = args.graph;
  1069. this.legend = args.legend;
  1070. var self = this;
  1071. var colorSafe = {};
  1072. this.addHighlightEvents = function (l) {
  1073. l.element.addEventListener( 'mouseover', function(e) {
  1074. self.legend.lines.forEach( function(line) {
  1075. if (l === line) return;
  1076. colorSafe[line.series.name] = colorSafe[line.series.name] || line.series.color;
  1077. line.series.color = d3.interpolateRgb(line.series.color, d3.rgb('#d8d8d8'))(0.8).toString();
  1078. } );
  1079. self.graph.update();
  1080. }, false );
  1081. l.element.addEventListener( 'mouseout', function(e) {
  1082. self.legend.lines.forEach( function(line) {
  1083. if (colorSafe[line.series.name]) {
  1084. line.series.color = colorSafe[line.series.name];
  1085. }
  1086. } );
  1087. self.graph.update();
  1088. }, false );
  1089. };
  1090. if (this.legend) {
  1091. this.legend.lines.forEach( function(l) {
  1092. self.addHighlightEvents(l);
  1093. } );
  1094. }
  1095. };
  1096. Rickshaw.namespace('Rickshaw.Graph.Behavior.Series.Order');
  1097. Rickshaw.Graph.Behavior.Series.Order = function(args) {
  1098. this.graph = args.graph;
  1099. this.legend = args.legend;
  1100. var self = this;
  1101. $(function() {
  1102. $(self.legend.list).sortable( {
  1103. containment: 'parent',
  1104. tolerance: 'pointer',
  1105. update: function( event, ui ) {
  1106. var series = [];
  1107. $(self.legend.list).find('li').each( function(index, item) {
  1108. if (!item.series) return;
  1109. series.push(item.series);
  1110. } );
  1111. for (var i = self.graph.series.length - 1; i >= 0; i--) {
  1112. self.graph.series[i] = series.shift();
  1113. }
  1114. self.graph.update();
  1115. }
  1116. } );
  1117. $(self.legend.list).disableSelection();
  1118. });
  1119. //hack to make jquery-ui sortable behave
  1120. this.graph.onUpdate( function() {
  1121. var h = window.getComputedStyle(self.legend.element).height;
  1122. self.legend.element.style.height = h;
  1123. } );
  1124. };
  1125. Rickshaw.namespace('Rickshaw.Graph.Behavior.Series.Toggle');
  1126. Rickshaw.Graph.Behavior.Series.Toggle = function(args) {
  1127. this.graph = args.graph;
  1128. this.legend = args.legend;
  1129. var self = this;
  1130. this.addAnchor = function(line) {
  1131. var anchor = document.createElement('a');
  1132. anchor.innerHTML = '&#10004;';
  1133. anchor.classList.add('action');
  1134. line.element.insertBefore(anchor, line.element.firstChild);
  1135. anchor.onclick = function(e) {
  1136. if (line.series.disabled) {
  1137. line.series.enable();
  1138. line.element.classList.remove('disabled');
  1139. } else {
  1140. line.series.disable();
  1141. line.element.classList.add('disabled');
  1142. }
  1143. }
  1144. var label = line.element.getElementsByTagName('span')[0];
  1145. label.onclick = function(e){
  1146. var disableAllOtherLines = line.series.disabled;
  1147. if ( ! disableAllOtherLines ) {
  1148. for ( var i = 0; i < self.legend.lines.length; i++ ) {
  1149. var l = self.legend.lines[i];
  1150. if ( line.series === l.series ) {
  1151. // noop
  1152. } else if ( l.series.disabled ) {
  1153. // noop
  1154. } else {
  1155. disableAllOtherLines = true;
  1156. break;
  1157. }
  1158. }
  1159. }
  1160. // show all or none
  1161. if ( disableAllOtherLines ) {
  1162. // these must happen first or else we try ( and probably fail ) to make a no line diagram
  1163. line.series.enable();
  1164. line.element.classList.remove('disabled');
  1165. self.legend.lines.forEach(function(l){
  1166. if ( line.series === l.series ) {
  1167. // noop
  1168. } else {
  1169. l.series.disable();
  1170. l.element.classList.add('disabled');
  1171. }
  1172. });
  1173. } else {
  1174. self.legend.lines.forEach(function(l){
  1175. l.series.enable();
  1176. l.element.classList.remove('disabled');
  1177. });
  1178. }
  1179. };
  1180. };
  1181. if (this.legend) {
  1182. $(this.legend.list).sortable( {
  1183. start: function(event, ui) {
  1184. ui.item.bind('no.onclick',
  1185. function(event) {
  1186. event.preventDefault();
  1187. }
  1188. );
  1189. },
  1190. stop: function(event, ui) {
  1191. setTimeout(function(){
  1192. ui.item.unbind('no.onclick');
  1193. }, 250);
  1194. }
  1195. })
  1196. this.legend.lines.forEach( function(l) {
  1197. self.addAnchor(l);
  1198. } );
  1199. }
  1200. this._addBehavior = function() {
  1201. this.graph.series.forEach( function(s) {
  1202. s.disable = function() {
  1203. if (self.graph.series.length <= 1) {
  1204. throw('only one series left');
  1205. }
  1206. s.disabled = true;
  1207. self.graph.update();
  1208. };
  1209. s.enable = function() {
  1210. s.disabled = false;
  1211. self.graph.update();
  1212. };
  1213. } );
  1214. };
  1215. this._addBehavior();
  1216. this.updateBehaviour = function () { this._addBehavior() };
  1217. };
  1218. Rickshaw.namespace('Rickshaw.Graph.HoverDetail');
  1219. Rickshaw.Graph.HoverDetail = Rickshaw.Class.create({
  1220. initialize: function(args) {
  1221. var graph = this.graph = args.graph;
  1222. this.xFormatter = args.xFormatter || function(x) {
  1223. return new Date( x * 1000 ).toUTCString();
  1224. };
  1225. this.yFormatter = args.yFormatter || function(y) {
  1226. return y.toFixed(2);
  1227. };
  1228. var element = this.element = document.createElement('div');
  1229. element.className = 'detail';
  1230. this.visible = true;
  1231. graph.element.appendChild(element);
  1232. this.lastEvent = null;
  1233. this._addListeners();
  1234. this.onShow = args.onShow;
  1235. this.onHide = args.onHide;
  1236. this.onRender = args.onRender;
  1237. this.formatter = args.formatter || this.formatter;
  1238. },
  1239. formatter: function(series, x, y, formattedX, formattedY, d) {
  1240. return series.name + ':&nbsp;' + formattedY;
  1241. },
  1242. update: function(e) {
  1243. e = e || this.lastEvent;
  1244. if (!e) return;
  1245. this.lastEvent = e;
  1246. if (!e.target.nodeName.match(/^(path|svg|rect)$/)) return;
  1247. var graph = this.graph;
  1248. var eventX = e.offsetX || e.layerX;
  1249. var eventY = e.offsetY || e.layerY;
  1250. var domainX = graph.x.invert(eventX);
  1251. var stackedData = graph.stackedData;
  1252. var topSeriesData = stackedData.slice(-1).shift();
  1253. var domainIndexScale = d3.scale.linear()
  1254. .domain([topSeriesData[0].x, topSeriesData.slice(-1).shift().x])
  1255. .range([0, topSeriesData.length]);
  1256. var approximateIndex = Math.floor(domainIndexScale(domainX));
  1257. var dataIndex = Math.min(approximateIndex || 0, stackedData[0].length - 1);
  1258. for (var i = approximateIndex; i < stackedData[0].length - 1;) {
  1259. if (!stackedData[0][i] || !stackedData[0][i + 1]) {
  1260. break;
  1261. }
  1262. if (stackedData[0][i].x <= domainX && stackedData[0][i + 1].x > domainX) {
  1263. dataIndex = i;
  1264. break;
  1265. }
  1266. if (stackedData[0][i + 1] <= domainX) { i++ } else { i-- }
  1267. }
  1268. var domainX = stackedData[0][dataIndex].x;
  1269. var formattedXValue = this.xFormatter(domainX);
  1270. var graphX = graph.x(domainX);
  1271. var order = 0;
  1272. var detail = graph.series.active()
  1273. .map( function(s) { return { order: order++, series: s, name: s.name, value: s.stack[dataIndex] } } );
  1274. var activeItem;
  1275. var sortFn = function(a, b) {
  1276. return (a.value.y0 + a.value.y) - (b.value.y0 + b.value.y);
  1277. };
  1278. var domainMouseY = graph.y.magnitude.invert(graph.element.offsetHeight - eventY);
  1279. detail.sort(sortFn).forEach( function(d) {
  1280. d.formattedYValue = (this.yFormatter.constructor == Array) ?
  1281. this.yFormatter[detail.indexOf(d)](d.value.y) :
  1282. this.yFormatter(d.value.y);
  1283. d.graphX = graphX;
  1284. d.graphY = graph.y(d.value.y0 + d.value.y);
  1285. if (domainMouseY > d.value.y0 && domainMouseY < d.value.y0 + d.value.y && !activeItem) {
  1286. activeItem = d;
  1287. d.active = true;
  1288. }
  1289. }, this );
  1290. this.element.innerHTML = '';
  1291. this.element.style.left = graph.x(domainX) + 'px';
  1292. if (this.visible) {
  1293. this.render( {
  1294. detail: detail,
  1295. domainX: domainX,
  1296. formattedXValue: formattedXValue,
  1297. mouseX: eventX,
  1298. mouseY: eventY
  1299. } );
  1300. }
  1301. },
  1302. hide: function() {
  1303. this.visible = false;
  1304. this.element.classList.add('inactive');
  1305. if (typeof this.onHide == 'function') {
  1306. this.onHide();
  1307. }
  1308. },
  1309. show: function() {
  1310. this.visible = true;
  1311. this.element.classList.remove('inactive');
  1312. if (typeof this.onShow == 'function') {
  1313. this.onShow();
  1314. }
  1315. },
  1316. render: function(args) {
  1317. var detail = args.detail;
  1318. var domainX = args.domainX;
  1319. var mouseX = args.mouseX;
  1320. var mouseY = args.mouseY;
  1321. var formattedXValue = args.formattedXValue;
  1322. var xLabel = document.createElement('div');
  1323. xLabel.className = 'x_label';
  1324. xLabel.innerHTML = formattedXValue;
  1325. this.element.appendChild(xLabel);
  1326. detail.forEach( function(d) {
  1327. var item = document.createElement('div');
  1328. item.className = 'item';
  1329. item.innerHTML = this.formatter(d.series, domainX, d.value.y, formattedXValue, d.formattedYValue, d);
  1330. item.style.top = this.graph.y(d.value.y0 + d.value.y) + 'px';
  1331. this.element.appendChild(item);
  1332. var dot = document.createElement('div');
  1333. dot.className = 'dot';
  1334. dot.style.top = item.style.top;
  1335. dot.style.borderColor = d.series.color;
  1336. this.element.appendChild(dot);
  1337. if (d.active) {
  1338. item.className = 'item active';
  1339. dot.className = 'dot active';
  1340. }
  1341. }, this );
  1342. this.show();
  1343. if (typeof this.onRender == 'function') {
  1344. this.onRender(args);
  1345. }
  1346. },
  1347. _addListeners: function() {
  1348. this.graph.element.addEventListener(
  1349. 'mousemove',
  1350. function(e) {
  1351. this.visible = true;
  1352. this.update(e)
  1353. }.bind(this),
  1354. false
  1355. );
  1356. this.graph.onUpdate( function() { this.update() }.bind(this) );
  1357. this.graph.element.addEventListener(
  1358. 'mouseout',
  1359. function(e) {
  1360. if (e.relatedTarget && !(e.relatedTarget.compareDocumentPosition(this.graph.element) & Node.DOCUMENT_POSITION_CONTAINS)) {
  1361. this.hide();
  1362. }
  1363. }.bind(this),
  1364. false
  1365. );
  1366. }
  1367. });
  1368. Rickshaw.namespace('Rickshaw.Graph.JSONP');
  1369. Rickshaw.Graph.JSONP = Rickshaw.Class.create( Rickshaw.Graph.Ajax, {
  1370. request: function() {
  1371. $.ajax( {
  1372. url: this.dataURL,
  1373. dataType: 'jsonp',
  1374. success: this.success.bind(this),
  1375. error: this.error.bind(this)
  1376. } );
  1377. }
  1378. } );
  1379. Rickshaw.namespace('Rickshaw.Graph.Legend');
  1380. Rickshaw.Graph.Legend = function(args) {
  1381. var element = this.element = args.element;
  1382. var graph = this.graph = args.graph;
  1383. var self = this;
  1384. element.classList.add('rickshaw_legend');
  1385. var list = this.list = document.createElement('ul');
  1386. element.appendChild(list);
  1387. var series = graph.series
  1388. .map( function(s) { return s } )
  1389. .reverse();
  1390. this.lines = [];
  1391. this.addLine = function (series) {
  1392. var line = document.createElement('li');
  1393. line.className = 'line';
  1394. var swatch = document.createElement('div');
  1395. swatch.className = 'swatch';
  1396. swatch.style.backgroundColor = series.color;
  1397. line.appendChild(swatch);
  1398. var label = document.createElement('span');
  1399. label.className = 'label';
  1400. label.innerHTML = series.name;
  1401. line.appendChild(label);
  1402. list.appendChild(line);
  1403. line.series = series;
  1404. if (series.noLegend) {
  1405. line.style.display = 'none';
  1406. }
  1407. var _line = { element: line, series: series };
  1408. if (self.shelving) {
  1409. self.shelving.addAnchor(_line);
  1410. self.shelving.updateBehaviour();
  1411. }
  1412. if (self.highlighter) {
  1413. self.highlighter.addHighlightEvents(_line);
  1414. }
  1415. self.lines.push(_line);
  1416. };
  1417. series.forEach( function(s) {
  1418. self.addLine(s);
  1419. } );
  1420. graph.onUpdate( function() {} );
  1421. };
  1422. Rickshaw.namespace('Rickshaw.Graph.RangeSlider');
  1423. Rickshaw.Graph.RangeSlider = function(args) {
  1424. var element = this.element = args.element;
  1425. var graph = this.graph = args.graph;
  1426. $( function() {
  1427. $(element).slider( {
  1428. range: true,
  1429. min: graph.dataDomain()[0],
  1430. max: graph.dataDomain()[1],
  1431. values: [
  1432. graph.dataDomain()[0],
  1433. graph.dataDomain()[1]
  1434. ],
  1435. slide: function( event, ui ) {
  1436. graph.window.xMin = ui.values[0];
  1437. graph.window.xMax = ui.values[1];
  1438. graph.update();
  1439. // if we're at an extreme, stick there
  1440. if (graph.dataDomain()[0] == ui.values[0]) {
  1441. graph.window.xMin = undefined;
  1442. }
  1443. if (graph.dataDomain()[1] == ui.values[1]) {
  1444. graph.window.xMax = undefined;
  1445. }
  1446. }
  1447. } );
  1448. } );
  1449. element[0].style.width = graph.width + 'px';
  1450. graph.onUpdate( function() {
  1451. var values = $(element).slider('option', 'values');
  1452. $(element).slider('option', 'min', graph.dataDomain()[0]);
  1453. $(element).slider('option', 'max', graph.dataDomain()[1]);
  1454. if (graph.window.xMin == undefined) {
  1455. values[0] = graph.dataDomain()[0];
  1456. }
  1457. if (graph.window.xMax == undefined) {
  1458. values[1] = graph.dataDomain()[1];
  1459. }
  1460. $(element).slider('option', 'values', values);
  1461. } );
  1462. };
  1463. Rickshaw.namespace("Rickshaw.Graph.Renderer");
  1464. Rickshaw.Graph.Renderer = Rickshaw.Class.create( {
  1465. initialize: function(args) {
  1466. this.graph = args.graph;
  1467. this.tension = args.tension || this.tension;
  1468. this.graph.unstacker = this.graph.unstacker || new Rickshaw.Graph.Unstacker( { graph: this.graph } );
  1469. this.configure(args);
  1470. },
  1471. seriesPathFactory: function() {
  1472. //implement in subclass
  1473. },
  1474. seriesStrokeFactory: function() {
  1475. // implement in subclass
  1476. },
  1477. defaults: function() {
  1478. return {
  1479. tension: 0.8,
  1480. strokeWidth: 2,
  1481. unstack: true,
  1482. padding: { top: 0.01, right: 0, bottom: 0.01, left: 0 },
  1483. stroke: false,
  1484. fill: false
  1485. };
  1486. },
  1487. domain: function() {
  1488. var values = [];
  1489. var stackedData = this.graph.stackedData || this.graph.stackData();
  1490. var topSeriesData = this.unstack ? stackedData : [ stackedData.slice(-1).shift() ];
  1491. topSeriesData.forEach( function(series) {
  1492. series.forEach( function(d) {
  1493. values.push( d.y + d.y0 );
  1494. } );
  1495. } );
  1496. var xMin = stackedData[0][0].x;
  1497. var xMax = stackedData[0][ stackedData[0].length - 1 ].x;
  1498. xMin -= (xMax - xMin) * this.padding.left;
  1499. xMax += (xMax - xMin) * this.padding.right;
  1500. var yMin = this.graph.min === 'auto' ? d3.min( values ) : this.graph.min || 0;
  1501. var yMax = this.graph.max || d3.max( values );
  1502. if (this.graph.min === 'auto' || yMin < 0) {
  1503. yMin -= (yMax - yMin) * this.padding.bottom;
  1504. }
  1505. if (this.graph.max === undefined) {
  1506. yMax += (yMax - yMin) * this.padding.top;
  1507. }
  1508. return { x: [xMin, xMax], y: [yMin, yMax] };
  1509. },
  1510. render: function() {
  1511. var graph = this.graph;
  1512. graph.vis.selectAll('*').remove();
  1513. var nodes = graph.vis.selectAll("path")
  1514. .data(this.graph.stackedData)
  1515. .enter().append("svg:path")
  1516. .attr("d", this.seriesPathFactory());
  1517. var i = 0;
  1518. graph.series.forEach( function(series) {
  1519. if (series.disabled) return;
  1520. series.path = nodes[0][i++];
  1521. this._styleSeries(series);
  1522. }, this );
  1523. },
  1524. _styleSeries: function(series) {
  1525. var fill = this.fill ? series.color : 'none';
  1526. var stroke = this.stroke ? series.color : 'none';
  1527. series.path.setAttribute('fill', fill);
  1528. series.path.setAttribute('stroke', stroke);
  1529. series.path.setAttribute('stroke-width', this.strokeWidth);
  1530. series.path.setAttribute('class', series.className);
  1531. },
  1532. configure: function(args) {
  1533. args = args || {};
  1534. Rickshaw.keys(this.defaults()).forEach( function(key) {
  1535. if (!args.hasOwnProperty(key)) {
  1536. this[key] = this[key] || this.graph[key] || this.defaults()[key];
  1537. return;
  1538. }
  1539. if (typeof this.defaults()[key] == 'object') {
  1540. Rickshaw.keys(this.defaults()[key]).forEach( function(k) {
  1541. this[key][k] =
  1542. args[key][k] !== undefined ? args[key][k] :
  1543. this[key][k] !== undefined ? this[key][k] :
  1544. this.defaults()[key][k];
  1545. }, this );
  1546. } else {
  1547. this[key] =
  1548. args[key] !== undefined ? args[key] :
  1549. this[key] !== undefined ? this[key] :
  1550. this.graph[key] !== undefined ? this.graph[key] :
  1551. this.defaults()[key];
  1552. }
  1553. }, this );
  1554. },
  1555. setStrokeWidth: function(strokeWidth) {
  1556. if (strokeWidth !== undefined) {
  1557. this.strokeWidth = strokeWidth;
  1558. }
  1559. },
  1560. setTension: function(tension) {
  1561. if (tension !== undefined) {
  1562. this.tension = tension;
  1563. }
  1564. }
  1565. } );
  1566. Rickshaw.namespace('Rickshaw.Graph.Renderer.Line');
  1567. Rickshaw.Graph.Renderer.Line = Rickshaw.Class.create( Rickshaw.Graph.Renderer, {
  1568. name: 'line',
  1569. defaults: function($super) {
  1570. return Rickshaw.extend( $super(), {
  1571. unstack: true,
  1572. fill: false,
  1573. stroke: true
  1574. } );
  1575. },
  1576. seriesPathFactory: function() {
  1577. var graph = this.graph;
  1578. return d3.svg.line()
  1579. .x( function(d) { return graph.x(d.x) } )
  1580. .y( function(d) { return graph.y(d.y) } )
  1581. .interpolate(this.graph.interpolation).tension(this.tension);
  1582. }
  1583. } );
  1584. Rickshaw.namespace('Rickshaw.Graph.Renderer.Stack');
  1585. Rickshaw.Graph.Renderer.Stack = Rickshaw.Class.create( Rickshaw.Graph.Renderer, {
  1586. name: 'stack',
  1587. defaults: function($super) {
  1588. return Rickshaw.extend( $super(), {
  1589. fill: true,
  1590. stroke: false,
  1591. unstack: false
  1592. } );
  1593. },
  1594. seriesPathFactory: function() {
  1595. var graph = this.graph;
  1596. return d3.svg.area()
  1597. .x( function(d) { return graph.x(d.x) } )
  1598. .y0( function(d) { return graph.y(d.y0) } )
  1599. .y1( function(d) { return graph.y(d.y + d.y0) } )
  1600. .interpolate(this.graph.interpolation).tension(this.tension);
  1601. }
  1602. } );
  1603. Rickshaw.namespace('Rickshaw.Graph.Renderer.Bar');
  1604. Rickshaw.Graph.Renderer.Bar = Rickshaw.Class.create( Rickshaw.Graph.Renderer, {
  1605. name: 'bar',
  1606. defaults: function($super) {
  1607. var defaults = Rickshaw.extend( $super(), {
  1608. gapSize: 0.05,
  1609. unstack: false
  1610. } );
  1611. delete defaults.tension;
  1612. return defaults;
  1613. },
  1614. initialize: function($super, args) {
  1615. args = args || {};
  1616. this.gapSize = args.gapSize || this.gapSize;
  1617. $super(args);
  1618. },
  1619. domain: function($super) {
  1620. var domain = $super();
  1621. var frequentInterval = this._frequentInterval();
  1622. domain.x[1] += parseInt(frequentInterval.magnitude);
  1623. return domain;
  1624. },
  1625. barWidth: function() {
  1626. var stackedData = this.graph.stackedData || this.graph.stackData();
  1627. var data = stackedData.slice(-1).shift();
  1628. var frequentInterval = this._frequentInterval();
  1629. var barWidth = this.graph.x(data[0].x + frequentInterval.magnitude * (1 - this.gapSize));
  1630. return barWidth;
  1631. },
  1632. render: function() {
  1633. var graph = this.graph;
  1634. graph.vis.selectAll('*').remove();
  1635. var barWidth = this.barWidth();
  1636. var barXOffset = 0;
  1637. var activeSeriesCount = graph.series.filter( function(s) { return !s.disabled; } ).length;
  1638. var seriesBarWidth = this.unstack ? barWidth / activeSeriesCount : barWidth;
  1639. var transform = function(d) {
  1640. // add a matrix transform for negative values
  1641. var matrix = [ 1, 0, 0, (d.y < 0 ? -1 : 1), 0, (d.y < 0 ? graph.y.magnitude(Math.abs(d.y)) * 2 : 0) ];
  1642. return "matrix(" + matrix.join(',') + ")";
  1643. };
  1644. graph.series.forEach( function(series) {
  1645. if (series.disabled) return;
  1646. var nodes = graph.vis.selectAll("path")
  1647. .data(series.stack)
  1648. .enter().append("svg:rect")
  1649. .attr("x", function(d) { return graph.x(d.x) + barXOffset })
  1650. .attr("y", function(d) { return (graph.y(d.y0 + Math.abs(d.y))) * (d.y < 0 ? -1 : 1 ) })
  1651. .attr("width", seriesBarWidth)
  1652. .attr("height", function(d) { return graph.y.magnitude(Math.abs(d.y)) })
  1653. .attr("transform", transform);
  1654. Array.prototype.forEach.call(nodes[0], function(n) {
  1655. n.setAttribute('fill', series.color);
  1656. } );
  1657. if (this.unstack) barXOffset += seriesBarWidth;
  1658. }, this );
  1659. },
  1660. _frequentInterval: function() {
  1661. var stackedData = this.graph.stackedData || this.graph.stackData();
  1662. var data = stackedData.slice(-1).shift();
  1663. var intervalCounts = {};
  1664. for (var i = 0; i < data.length - 1; i++) {
  1665. var interval = data[i + 1].x - data[i].x;
  1666. intervalCounts[interval] = intervalCounts[interval] || 0;
  1667. intervalCounts[interval]++;
  1668. }
  1669. var frequentInterval = { count: 0 };
  1670. Rickshaw.keys(intervalCounts).forEach( function(i) {
  1671. if (frequentInterval.count < intervalCounts[i]) {
  1672. frequentInterval = {
  1673. count: intervalCounts[i],
  1674. magnitude: i
  1675. };
  1676. }
  1677. } );
  1678. this._frequentInterval = function() { return frequentInterval };
  1679. return frequentInterval;
  1680. }
  1681. } );
  1682. Rickshaw.namespace('Rickshaw.Graph.Renderer.Area');
  1683. Rickshaw.Graph.Renderer.Area = Rickshaw.Class.create( Rickshaw.Graph.Renderer, {
  1684. name: 'area',
  1685. defaults: function($super) {
  1686. return Rickshaw.extend( $super(), {
  1687. unstack: false,
  1688. fill: false,
  1689. stroke: false
  1690. } );
  1691. },
  1692. seriesPathFactory: function() {
  1693. var graph = this.graph;
  1694. return d3.svg.area()
  1695. .x( function(d) { return graph.x(d.x) } )
  1696. .y0( function(d) { return graph.y(d.y0) } )
  1697. .y1( function(d) { return graph.y(d.y + d.y0) } )
  1698. .interpolate(graph.interpolation).tension(this.tension);
  1699. },
  1700. seriesStrokeFactory: function() {
  1701. var graph = this.graph;
  1702. return d3.svg.line()
  1703. .x( function(d) { return graph.x(d.x) } )
  1704. .y( function(d) { return graph.y(d.y + d.y0) } )
  1705. .interpolate(graph.interpolation).tension(this.tension);
  1706. },
  1707. render: function() {
  1708. var graph = this.graph;
  1709. graph.vis.selectAll('*').remove();
  1710. var nodes = graph.vis.selectAll("path")
  1711. .data(this.graph.stackedData)
  1712. .enter().insert("svg:g", 'g');
  1713. nodes.append("svg:path")
  1714. .attr("d", this.seriesPathFactory())
  1715. .attr("class", 'area');
  1716. if (this.stroke) {
  1717. nodes.append("svg:path")
  1718. .attr("d", this.seriesStrokeFactory())
  1719. .attr("class", 'line');
  1720. }
  1721. var i = 0;
  1722. graph.series.forEach( function(series) {
  1723. if (series.disabled) return;
  1724. series.path = nodes[0][i++];
  1725. this._styleSeries(series);
  1726. }, this );
  1727. },
  1728. _styleSeries: function(series) {
  1729. if (!series.path) return;
  1730. d3.select(series.path).select('.area')
  1731. .attr('fill', series.color);
  1732. if (this.stroke) {
  1733. d3.select(series.path).select('.line')
  1734. .attr('fill', 'none')
  1735. .attr('stroke', series.stroke || d3.interpolateRgb(series.color, 'black')(0.125))
  1736. .attr('stroke-width', this.strokeWidth);
  1737. }
  1738. if (series.className) {
  1739. series.path.setAttribute('class', series.className);
  1740. }
  1741. }
  1742. } );
  1743. Rickshaw.namespace('Rickshaw.Graph.Renderer.ScatterPlot');
  1744. Rickshaw.Graph.Renderer.ScatterPlot = Rickshaw.Class.create( Rickshaw.Graph.Renderer, {
  1745. name: 'scatterplot',
  1746. defaults: function($super) {
  1747. return Rickshaw.extend( $super(), {
  1748. unstack: true,
  1749. fill: true,
  1750. stroke: false,
  1751. padding:{ top: 0.01, right: 0.01, bottom: 0.01, left: 0.01 },
  1752. dotSize: 4
  1753. } );
  1754. },
  1755. initialize: function($super, args) {
  1756. $super(args);
  1757. },
  1758. render: function() {
  1759. var graph = this.graph;
  1760. graph.vis.selectAll('*').remove();
  1761. graph.series.forEach( function(series) {
  1762. if (series.disabled) return;
  1763. var nodes = graph.vis.selectAll("path")
  1764. .data(series.stack)
  1765. .enter().append("svg:circle")
  1766. .attr("cx", function(d) { return graph.x(d.x) })
  1767. .attr("cy", function(d) { return graph.y(d.y) })
  1768. .attr("r", function(d) { return ("r" in d) ? d.r : graph.renderer.dotSize});
  1769. Array.prototype.forEach.call(nodes[0], function(n) {
  1770. n.setAttribute('fill', series.color);
  1771. } );
  1772. }, this );
  1773. }
  1774. } );
  1775. Rickshaw.namespace('Rickshaw.Graph.Smoother');
  1776. Rickshaw.Graph.Smoother = function(args) {
  1777. this.graph = args.graph;
  1778. this.element = args.element;
  1779. var self = this;
  1780. this.aggregationScale = 1;
  1781. if (this.element) {
  1782. $( function() {
  1783. $(self.element).slider( {
  1784. min: 1,
  1785. max: 100,
  1786. slide: function( event, ui ) {
  1787. self.setScale(ui.value);
  1788. self.graph.update();
  1789. }
  1790. } );
  1791. } );
  1792. }
  1793. self.graph.stackData.hooks.data.push( {
  1794. name: 'smoother',
  1795. orderPosition: 50,
  1796. f: function(data) {
  1797. var aggregatedData = [];
  1798. data.forEach( function(seriesData) {
  1799. var aggregatedSeriesData = [];
  1800. while (seriesData.length) {
  1801. var avgX = 0, avgY = 0;
  1802. var slice = seriesData.splice(0, self.aggregationScale);
  1803. slice.forEach( function(d) {
  1804. avgX += d.x / slice.length;
  1805. avgY += d.y / slice.length;
  1806. } );
  1807. aggregatedSeriesData.push( { x: avgX, y: avgY } );
  1808. }
  1809. aggregatedData.push(aggregatedSeriesData);
  1810. } );
  1811. return aggregatedData;
  1812. }
  1813. } );
  1814. this.setScale = function(scale) {
  1815. if (scale < 1) {
  1816. throw "scale out of range: " + scale;
  1817. }
  1818. this.aggregationScale = scale;
  1819. this.graph.update();
  1820. }
  1821. };
  1822. Rickshaw.namespace('Rickshaw.Graph.Unstacker');
  1823. Rickshaw.Graph.Unstacker = function(args) {
  1824. this.graph = args.graph;
  1825. var self = this;
  1826. this.graph.stackData.hooks.after.push( {
  1827. name: 'unstacker',
  1828. f: function(data) {
  1829. if (!self.graph.renderer.unstack) return data;
  1830. data.forEach( function(seriesData) {
  1831. seriesData.forEach( function(d) {
  1832. d.y0 = 0;
  1833. } );
  1834. } );
  1835. return data;
  1836. }
  1837. } );
  1838. };
  1839. Rickshaw.namespace('Rickshaw.Series');
  1840. Rickshaw.Series = Rickshaw.Class.create( Array, {
  1841. initialize: function (data, palette, options) {
  1842. options = options || {}
  1843. this.palette = new Rickshaw.Color.Palette(palette);
  1844. this.timeBase = typeof(options.timeBase) === 'undefined' ?
  1845. Math.floor(new Date().getTime() / 1000) :
  1846. options.timeBase;
  1847. var timeInterval = typeof(options.timeInterval) == 'undefined' ?
  1848. 1000 :
  1849. options.timeInterval;
  1850. this.setTimeInterval(timeInterval);
  1851. if (data && (typeof(data) == "object") && (data instanceof Array)) {
  1852. data.forEach( function(item) { this.addItem(item) }, this );
  1853. }
  1854. },
  1855. addItem: function(item) {
  1856. if (typeof(item.name) === 'undefined') {
  1857. throw('addItem() needs a name');
  1858. }
  1859. item.color = (item.color || this.palette.color(item.name));
  1860. item.data = (item.data || []);
  1861. // backfill, if necessary
  1862. if ((item.data.length == 0) && this.length && (this.getIndex() > 0)) {
  1863. this[0].data.forEach( function(plot) {
  1864. item.data.push({ x: plot.x, y: 0 });
  1865. } );
  1866. } else if (item.data.length == 0) {
  1867. item.data.push({ x: this.timeBase - (this.timeInterval || 0), y: 0 });
  1868. }
  1869. this.push(item);
  1870. if (this.legend) {
  1871. this.legend.addLine(this.itemByName(item.name));
  1872. }
  1873. },
  1874. addData: function(data) {
  1875. var index = this.getIndex();
  1876. Rickshaw.keys(data).forEach( function(name) {
  1877. if (! this.itemByName(name)) {
  1878. this.addItem({ name: name });
  1879. }
  1880. }, this );
  1881. this.forEach( function(item) {
  1882. item.data.push({
  1883. x: (index * this.timeInterval || 1) + this.timeBase,
  1884. y: (data[item.name] || 0)
  1885. });
  1886. }, this );
  1887. },
  1888. getIndex: function () {
  1889. return (this[0] && this[0].data && this[0].data.length) ? this[0].data.length : 0;
  1890. },
  1891. itemByName: function(name) {
  1892. for (var i = 0; i < this.length; i++) {
  1893. if (this[i].name == name)
  1894. return this[i];
  1895. }
  1896. },
  1897. setTimeInterval: function(iv) {
  1898. this.timeInterval = iv / 1000;
  1899. },
  1900. setTimeBase: function (t) {
  1901. this.timeBase = t;
  1902. },
  1903. dump: function() {
  1904. var data = {
  1905. timeBase: this.timeBase,
  1906. timeInterval: this.timeInterval,
  1907. items: []
  1908. };
  1909. this.forEach( function(item) {
  1910. var newItem = {
  1911. color: item.color,
  1912. name: item.name,
  1913. data: []
  1914. };
  1915. item.data.forEach( function(plot) {
  1916. newItem.data.push({ x: plot.x, y: plot.y });
  1917. } );
  1918. data.items.push(newItem);
  1919. } );
  1920. return data;
  1921. },
  1922. load: function(data) {
  1923. if (data.timeInterval) {
  1924. this.timeInterval = data.timeInterval;
  1925. }
  1926. if (data.timeBase) {
  1927. this.timeBase = data.timeBase;
  1928. }
  1929. if (data.items) {
  1930. data.items.forEach( function(item) {
  1931. this.push(item);
  1932. if (this.legend) {
  1933. this.legend.addLine(this.itemByName(item.name));
  1934. }
  1935. }, this );
  1936. }
  1937. }
  1938. } );
  1939. Rickshaw.Series.zeroFill = function(series) {
  1940. var x;
  1941. var i = 0;
  1942. var data = series.map( function(s) { return s.data } );
  1943. while ( i < Math.max.apply(null, data.map( function(d) { return d.length } )) ) {
  1944. x = Math.min.apply( null,
  1945. data
  1946. .filter(function(d) { return d[i] })
  1947. .map(function(d) { return d[i].x })
  1948. );
  1949. data.forEach( function(d) {
  1950. if (!d[i] || d[i].x != x) {
  1951. d.splice(i, 0, { x: x, y: 0 });
  1952. }
  1953. } );
  1954. i++;
  1955. }
  1956. };
  1957. Rickshaw.namespace('Rickshaw.Series.FixedDuration');
  1958. Rickshaw.Series.FixedDuration = Rickshaw.Class.create(Rickshaw.Series, {
  1959. initialize: function (data, palette, options) {
  1960. var options = options || {}
  1961. if (typeof(options.timeInterval) === 'undefined') {
  1962. throw new Error('FixedDuration series requires timeInterval');
  1963. }
  1964. if (typeof(options.maxDataPoints) === 'undefined') {
  1965. throw new Error('FixedDuration series requires maxDataPoints');
  1966. }
  1967. this.palette = new Rickshaw.Color.Palette(palette);
  1968. this.timeBase = typeof(options.timeBase) === 'undefined' ? Math.floor(new Date().getTime() / 1000) : options.timeBase;
  1969. this.setTimeInterval(options.timeInterval);
  1970. if (this[0] && this[0].data && this[0].data.length) {
  1971. this.currentSize = this[0].data.length;
  1972. this.currentIndex = this[0].data.length;
  1973. } else {
  1974. this.currentSize = 0;
  1975. this.currentIndex = 0;
  1976. }
  1977. this.maxDataPoints = options.maxDataPoints;
  1978. if (data && (typeof(data) == "object") && (data instanceof Array)) {
  1979. data.forEach( function (item) { this.addItem(item) }, this );
  1980. this.currentSize += 1;
  1981. this.currentIndex += 1;
  1982. }
  1983. // reset timeBase for zero-filled values if needed
  1984. this.timeBase -= (this.maxDataPoints - this.currentSize) * this.timeInterval;
  1985. // zero-fill up to maxDataPoints size if we don't have that much data yet
  1986. if ((typeof(this.maxDataPoints) !== 'undefined') && (this.currentSize < this.maxDataPoints)) {
  1987. for (var i = this.maxDataPoints - this.currentSize - 1; i > 0; i--) {
  1988. this.currentSize += 1;
  1989. this.currentIndex += 1;
  1990. this.forEach( function (item) {
  1991. item.data.unshift({ x: ((i-1) * this.timeInterval || 1) + this.timeBase, y: 0, i: i });
  1992. }, this );
  1993. }
  1994. }
  1995. },
  1996. addData: function($super, data) {
  1997. $super(data)
  1998. this.currentSize += 1;
  1999. this.currentIndex += 1;
  2000. if (this.maxDataPoints !== undefined) {
  2001. while (this.currentSize > this.maxDataPoints) {
  2002. this.dropData();
  2003. }
  2004. }
  2005. },
  2006. dropData: function() {
  2007. this.forEach(function(item) {
  2008. item.data.splice(0, 1);
  2009. } );
  2010. this.currentSize -= 1;
  2011. },
  2012. getIndex: function () {
  2013. return this.currentIndex;
  2014. }
  2015. } );