Merge lp:~lucio.torre/graphite/add-flot into lp:~graphite-dev/graphite/main

Proposed by Lucio Torre on 2010-11-01
Status: Merged
Merge reported by: chrismd
Merged at revision: not available
Proposed branch: lp:~lucio.torre/graphite/add-flot
Merge into: lp:~graphite-dev/graphite/main
Diff against target: 4383 lines
To merge this branch: bzr merge lp:~lucio.torre/graphite/add-flot
Reviewer Review Type Date Requested Status
chrismd 2010-11-01 Approve on 2011-07-10
Review via email: mp+39768@code.launchpad.net

Description of the Change

Adds a new interface for rendering graphs with the following features:
- rendering is done client side
- you can mouse-over data points to see their value and to which series they belong
- you can zoom into portions of the data
- you have an overview of the plot
- you can move the series between any of the two axis
- autocomplete for metric input
- faster manipulation of graph series

We are currently working with this live to see how well it copes with lots of data. So far it seems to work ok. I find it very useful to have two axis and an easy way to tell which series is the one that i am pointing with the mouse (with many series colors become very similar)

Please let me know what you think.

To post a comment you must log in.
chrismd (chrismd) wrote :

Sorry for the extremely long delay. I've finally reviewed your branch and I must say it is a pretty cool UI. I fixed a few merge issues (I think you forgot to bzr add a delete.png file you were replacing delete.gif with). And it is now in trunk, thanks and again sorry this took so long.

chrismd (chrismd) :
review: Approve
jeromy (fuji246) wrote :

Hi, is there any update on this? I've made an improved version, and added autoupdate and timezone suppport, and do some enhancement for the ui.

Amos Shapira (amos-shapira) wrote :

I'm not a member of the core team but from following them I understand that they moved the code repository to Github: https://github.com/graphite-project.

Maybe it could help if you submitted the new update there.

Thanks for all your work!

Lucio Torre (lucio.torre) wrote :

This has been merged and can be found on the "flot (experimental)" link in the main page.

Preview Diff

1=== added file 'webapp/content/css/jquery.autocomplete.css'
2--- webapp/content/css/jquery.autocomplete.css 1970-01-01 00:00:00 +0000
3+++ webapp/content/css/jquery.autocomplete.css 2010-11-01 18:48:50 +0000
4@@ -0,0 +1,48 @@
5+.ac_results {
6+ padding: 0px;
7+ border: 1px solid black;
8+ background-color: white;
9+ overflow: hidden;
10+ z-index: 99999;
11+}
12+
13+.ac_results ul {
14+ width: 100%;
15+ list-style-position: outside;
16+ list-style: none;
17+ padding: 0;
18+ margin: 0;
19+}
20+
21+.ac_results li {
22+ margin: 0px;
23+ padding: 2px 5px;
24+ cursor: default;
25+ display: block;
26+ /*
27+ if width will be 100% horizontal scrollbar will apear
28+ when scroll mode will be used
29+ */
30+ /*width: 100%;*/
31+ font: menu;
32+ font-size: 12px;
33+ /*
34+ it is very important, if line-height not setted or setted
35+ in relative units scroll will be broken in firefox
36+ */
37+ line-height: 16px;
38+ overflow: hidden;
39+}
40+
41+.ac_loading {
42+ background: white url('../img/indicator.png') right center no-repeat;
43+}
44+
45+.ac_odd {
46+ background-color: #eee;
47+}
48+
49+.ac_over {
50+ background-color: #0A246A;
51+ color: white;
52+}
53
54=== added file 'webapp/content/css/table.css'
55--- webapp/content/css/table.css 1970-01-01 00:00:00 +0000
56+++ webapp/content/css/table.css 2010-11-01 18:48:50 +0000
57@@ -0,0 +1,1 @@
58+body {
59 color: #4f6b72;
60 background: #E6EAE9;
61}
62
63a {
64 color: #c75f3e;
65}
66
67#canvas {
68 border-right: 1px solid #C1DAD7;
69 border-bottom: 1px solid #C1DAD7;
70 background: #fff;
71
72 color: #4f6b72;
73}
74
75#title {
76 font: bold 11px "Trebuchet MS", Verdana, Arial, Helvetica, sans-serif;
77 color: #4f6b72;
78 border-right: 1px solid #C1DAD7;
79 border-bottom: 1px solid #C1DAD7;
80 border-top: 1px solid #C1DAD7;
81 letter-spacing: 2px;
82 text-transform: uppercase;
83 text-align: left;
84 padding: 6px 6px 6px 12px;
85 background: #CAE8EA;
86}
87
88.styledtable th {
89 font: bold 11px "Trebuchet MS", Verdana, Arial, Helvetica, sans-serif;
90 color: #4f6b72;
91 border-right: 1px solid #C1DAD7;
92 border-bottom: 1px solid #C1DAD7;
93 border-top: 1px solid #C1DAD7;
94 letter-spacing: 2px;
95 text-transform: uppercase;
96 text-align: left;
97 padding: 6px 6px 6px 12px;
98 background: #CAE8EA;
99}
100
101
102
103.styledtable td {
104 border-right: 1px solid #C1DAD7;
105 border-bottom: 1px solid #C1DAD7;
106 background: #fff;
107 padding: 6px 6px 6px 12px;
108 color: #4f6b72;
109}
110
111\ No newline at end of file
112
113=== removed file 'webapp/content/img/delete.gif'
114Binary files webapp/content/img/delete.gif 2010-02-26 16:15:44 +0000 and webapp/content/img/delete.gif 1970-01-01 00:00:00 +0000 differ
115=== added file 'webapp/content/img/delete.png'
116Binary files webapp/content/img/delete.png 1970-01-01 00:00:00 +0000 and webapp/content/img/delete.png 2010-11-01 18:48:50 +0000 differ
117=== added file 'webapp/content/img/indicator.png'
118Binary files webapp/content/img/indicator.png 1970-01-01 00:00:00 +0000 and webapp/content/img/indicator.png 2010-11-01 18:48:50 +0000 differ
119=== added file 'webapp/content/js/jquery.autocomplete.js'
120--- webapp/content/js/jquery.autocomplete.js 1970-01-01 00:00:00 +0000
121+++ webapp/content/js/jquery.autocomplete.js 2010-11-01 18:48:50 +0000
122@@ -0,0 +1,762 @@
123+/*
124+ * Autocomplete - jQuery plugin 1.1pre
125+ *
126+ * Copyright (c) 2007 Dylan Verheul, Dan G. Switzer, Anjesh Tuladhar, Jörn Zaefferer
127+ *
128+ * Dual licensed under the MIT and GPL licenses:
129+ * http://www.opensource.org/licenses/mit-license.php
130+ * http://www.gnu.org/licenses/gpl.html
131+ *
132+ * Revision: $Id: jquery.autocomplete.js 5785 2008-07-12 10:37:33Z joern.zaefferer $
133+ *
134+ */
135+
136+;(function($) {
137+
138+$.fn.extend({
139+ autocomplete: function(urlOrData, options) {
140+ var isUrl = typeof urlOrData == "string";
141+ options = $.extend({}, $.Autocompleter.defaults, {
142+ url: isUrl ? urlOrData : null,
143+ data: isUrl ? null : urlOrData,
144+ delay: isUrl ? $.Autocompleter.defaults.delay : 10,
145+ max: options && !options.scroll ? 10 : 150
146+ }, options);
147+
148+ // if highlight is set to false, replace it with a do-nothing function
149+ options.highlight = options.highlight || function(value) { return value; };
150+
151+ // if the formatMatch option is not specified, then use formatItem for backwards compatibility
152+ options.formatMatch = options.formatMatch || options.formatItem;
153+
154+ return this.each(function() {
155+ new $.Autocompleter(this, options);
156+ });
157+ },
158+ result: function(handler) {
159+ return this.bind("result", handler);
160+ },
161+ search: function(handler) {
162+ return this.trigger("search", [handler]);
163+ },
164+ flushCache: function() {
165+ return this.trigger("flushCache");
166+ },
167+ setOptions: function(options){
168+ return this.trigger("setOptions", [options]);
169+ },
170+ unautocomplete: function() {
171+ return this.trigger("unautocomplete");
172+ }
173+});
174+
175+$.Autocompleter = function(input, options) {
176+
177+ var KEY = {
178+ UP: 38,
179+ DOWN: 40,
180+ DEL: 46,
181+ TAB: 9,
182+ RETURN: 13,
183+ ESC: 27,
184+ COMMA: 188,
185+ PAGEUP: 33,
186+ PAGEDOWN: 34,
187+ BACKSPACE: 8
188+ };
189+
190+ // Create $ object for input element
191+ var $input = $(input).attr("autocomplete", "off").addClass(options.inputClass);
192+
193+ var timeout;
194+ var previousValue = "";
195+ var cache = $.Autocompleter.Cache(options);
196+ var hasFocus = 0;
197+ var lastKeyPressCode;
198+ var config = {
199+ mouseDownOnSelect: false
200+ };
201+ var select = $.Autocompleter.Select(options, input, selectCurrent, config);
202+
203+ var blockSubmit;
204+
205+ // prevent form submit in opera when selecting with return key
206+ $.browser.opera && $(input.form).bind("submit.autocomplete", function() {
207+ if (blockSubmit) {
208+ blockSubmit = false;
209+ return false;
210+ }
211+ });
212+
213+ // only opera doesn't trigger keydown multiple times while pressed, others don't work with keypress at all
214+ $input.bind(($.browser.opera ? "keypress" : "keydown") + ".autocomplete", function(event) {
215+ // track last key pressed
216+ lastKeyPressCode = event.keyCode;
217+ switch(event.keyCode) {
218+
219+ case KEY.UP:
220+ event.preventDefault();
221+ if ( select.visible() ) {
222+ select.prev();
223+ } else {
224+ onChange(0, true);
225+ }
226+ break;
227+
228+ case KEY.DOWN:
229+ event.preventDefault();
230+ if ( select.visible() ) {
231+ select.next();
232+ } else {
233+ onChange(0, true);
234+ }
235+ break;
236+
237+ case KEY.PAGEUP:
238+ event.preventDefault();
239+ if ( select.visible() ) {
240+ select.pageUp();
241+ } else {
242+ onChange(0, true);
243+ }
244+ break;
245+
246+ case KEY.PAGEDOWN:
247+ event.preventDefault();
248+ if ( select.visible() ) {
249+ select.pageDown();
250+ } else {
251+ onChange(0, true);
252+ }
253+ break;
254+
255+ // matches also semicolon
256+ case options.multiple && $.trim(options.multipleSeparator) == "," && KEY.COMMA:
257+ case KEY.TAB:
258+ case KEY.RETURN:
259+ if( selectCurrent() ) {
260+ // stop default to prevent a form submit, Opera needs special handling
261+ event.preventDefault();
262+ blockSubmit = true;
263+ return false;
264+ }
265+ break;
266+
267+ case KEY.ESC:
268+ select.hide();
269+ break;
270+
271+ default:
272+ clearTimeout(timeout);
273+ timeout = setTimeout(onChange, options.delay);
274+ break;
275+ }
276+ }).focus(function(){
277+ // track whether the field has focus, we shouldn't process any
278+ // results if the field no longer has focus
279+ hasFocus++;
280+ }).blur(function() {
281+ hasFocus = 0;
282+ if (!config.mouseDownOnSelect) {
283+ hideResults();
284+ }
285+ }).click(function() {
286+ // show select when clicking in a focused field
287+ if ( hasFocus++ > 1 && !select.visible() ) {
288+ onChange(0, true);
289+ }
290+ }).bind("search", function() {
291+ // TODO why not just specifying both arguments?
292+ var fn = (arguments.length > 1) ? arguments[1] : null;
293+ function findValueCallback(q, data) {
294+ var result;
295+ if( data && data.length ) {
296+ for (var i=0; i < data.length; i++) {
297+ if( data[i].result.toLowerCase() == q.toLowerCase() ) {
298+ result = data[i];
299+ break;
300+ }
301+ }
302+ }
303+ if( typeof fn == "function" ) fn(result);
304+ else $input.trigger("result", result && [result.data, result.value]);
305+ }
306+ $.each(trimWords($input.val()), function(i, value) {
307+ request(value, findValueCallback, findValueCallback);
308+ });
309+ }).bind("flushCache", function() {
310+ cache.flush();
311+ }).bind("setOptions", function() {
312+ $.extend(options, arguments[1]);
313+ // if we've updated the data, repopulate
314+ if ( "data" in arguments[1] )
315+ cache.populate();
316+ }).bind("unautocomplete", function() {
317+ select.unbind();
318+ $input.unbind();
319+ $(input.form).unbind(".autocomplete");
320+ });
321+
322+
323+ function selectCurrent() {
324+ var selected = select.selected();
325+ if( !selected )
326+ return false;
327+
328+ var v = selected.result;
329+ previousValue = v;
330+
331+ if ( options.multiple ) {
332+ var words = trimWords($input.val());
333+ if ( words.length > 1 ) {
334+ v = words.slice(0, words.length - 1).join( options.multipleSeparator ) + options.multipleSeparator + v;
335+ }
336+ v += options.multipleSeparator;
337+ }
338+
339+ $input.val(v);
340+ hideResultsNow();
341+ $input.trigger("result", [selected.data, selected.value]);
342+ return true;
343+ }
344+
345+ function onChange(crap, skipPrevCheck) {
346+ if( lastKeyPressCode == KEY.DEL ) {
347+ select.hide();
348+ return;
349+ }
350+
351+ var currentValue = $input.val();
352+
353+ if ( !skipPrevCheck && currentValue == previousValue )
354+ return;
355+
356+ previousValue = currentValue;
357+
358+ currentValue = lastWord(currentValue);
359+ if ( currentValue.length >= options.minChars) {
360+ $input.addClass(options.loadingClass);
361+ if (!options.matchCase)
362+ currentValue = currentValue.toLowerCase();
363+ request(currentValue, receiveData, hideResultsNow);
364+ } else {
365+ stopLoading();
366+ select.hide();
367+ }
368+ };
369+
370+ function trimWords(value) {
371+ if ( !value ) {
372+ return [""];
373+ }
374+ var words = value.split( options.multipleSeparator );
375+ var result = [];
376+ $.each(words, function(i, value) {
377+ if ( $.trim(value) )
378+ result[i] = $.trim(value);
379+ });
380+ return result;
381+ }
382+
383+ function lastWord(value) {
384+ if ( !options.multiple )
385+ return value;
386+ var words = trimWords(value);
387+ return words[words.length - 1];
388+ }
389+
390+ // fills in the input box w/the first match (assumed to be the best match)
391+ // q: the term entered
392+ // sValue: the first matching result
393+ function autoFill(q, sValue){
394+ // autofill in the complete box w/the first match as long as the user hasn't entered in more data
395+ // if the last user key pressed was backspace, don't autofill
396+ if( options.autoFill && (lastWord($input.val()).toLowerCase() == q.toLowerCase()) && lastKeyPressCode != KEY.BACKSPACE ) {
397+ // fill in the value (keep the case the user has typed)
398+ $input.val($input.val() + sValue.substring(lastWord(previousValue).length));
399+ // select the portion of the value not typed by the user (so the next character will erase)
400+ $.Autocompleter.Selection(input, previousValue.length, previousValue.length + sValue.length);
401+ }
402+ };
403+
404+ function hideResults() {
405+ clearTimeout(timeout);
406+ timeout = setTimeout(hideResultsNow, 200);
407+ };
408+
409+ function hideResultsNow() {
410+ var wasVisible = select.visible();
411+ select.hide();
412+ clearTimeout(timeout);
413+ stopLoading();
414+ if (options.mustMatch) {
415+ // call search and run callback
416+ $input.search(
417+ function (result){
418+ // if no value found, clear the input box
419+ if( !result ) {
420+ if (options.multiple) {
421+ var words = trimWords($input.val()).slice(0, -1);
422+ $input.val( words.join(options.multipleSeparator) + (words.length ? options.multipleSeparator : "") );
423+ }
424+ else
425+ $input.val( "" );
426+ }
427+ }
428+ );
429+ }
430+ if (wasVisible)
431+ // position cursor at end of input field
432+ $.Autocompleter.Selection(input, input.value.length, input.value.length);
433+ };
434+
435+ function receiveData(q, data) {
436+ if ( data && data.length && hasFocus ) {
437+ stopLoading();
438+ select.display(data, q);
439+ autoFill(q, data[0].value);
440+ select.show();
441+ } else {
442+ hideResultsNow();
443+ }
444+ };
445+
446+ function request(term, success, failure) {
447+ if (!options.matchCase)
448+ term = term.toLowerCase();
449+ var data = cache.load(term);
450+ // recieve the cached data
451+ if (data && data.length) {
452+ success(term, data);
453+ // if an AJAX url has been supplied, try loading the data now
454+ } else if( (typeof options.url == "string") && (options.url.length > 0) ){
455+
456+ var extraParams = {
457+ timestamp: +new Date()
458+ };
459+ $.each(options.extraParams, function(key, param) {
460+ extraParams[key] = typeof param == "function" ? param() : param;
461+ });
462+
463+ $.ajax({
464+ // try to leverage ajaxQueue plugin to abort previous requests
465+ mode: "abort",
466+ // limit abortion to this input
467+ port: "autocomplete" + input.name,
468+ dataType: options.dataType,
469+ url: options.url,
470+ data: $.extend({
471+ q: lastWord(term),
472+ limit: options.max
473+ }, extraParams),
474+ success: function(data) {
475+ var parsed = options.parse && options.parse(data) || parse(data);
476+ cache.add(term, parsed);
477+ success(term, parsed);
478+ }
479+ });
480+ } else {
481+ // if we have a failure, we need to empty the list -- this prevents the the [TAB] key from selecting the last successful match
482+ select.emptyList();
483+ failure(term);
484+ }
485+ };
486+
487+ function parse(data) {
488+ var parsed = [];
489+ var rows = data.split("\n");
490+ for (var i=0; i < rows.length; i++) {
491+ var row = $.trim(rows[i]);
492+ if (row) {
493+ row = row.split("|");
494+ parsed[parsed.length] = {
495+ data: row,
496+ value: row[0],
497+ result: options.formatResult && options.formatResult(row, row[0]) || row[0]
498+ };
499+ }
500+ }
501+ return parsed;
502+ };
503+
504+ function stopLoading() {
505+ $input.removeClass(options.loadingClass);
506+ };
507+
508+};
509+
510+$.Autocompleter.defaults = {
511+ inputClass: "ac_input",
512+ resultsClass: "ac_results",
513+ loadingClass: "ac_loading",
514+ minChars: 1,
515+ delay: 400,
516+ matchCase: false,
517+ matchSubset: true,
518+ matchContains: false,
519+ cacheLength: 10,
520+ max: 100,
521+ mustMatch: false,
522+ extraParams: {},
523+ selectFirst: true,
524+ formatItem: function(row) { return row[0]; },
525+ formatMatch: null,
526+ autoFill: false,
527+ width: 0,
528+ multiple: false,
529+ multipleSeparator: ", ",
530+ highlight: function(value, term) {
531+ return value.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + term.replace(/([\^\$\(\)\[\]\{\}\*\.\+\?\|\\])/gi, "\\$1") + ")(?![^<>]*>)(?![^&;]+;)", "gi"), "<strong>$1</strong>");
532+ },
533+ scroll: true,
534+ scrollHeight: 180
535+};
536+
537+$.Autocompleter.Cache = function(options) {
538+
539+ var data = {};
540+ var length = 0;
541+
542+ function matchSubset(s, sub) {
543+ if (!options.matchCase)
544+ s = s.toLowerCase();
545+ var i = s.indexOf(sub);
546+ if (options.matchContains == "word"){
547+ i = s.toLowerCase().search("\\b" + sub.toLowerCase());
548+ }
549+ if (i == -1) return false;
550+ return i == 0 || options.matchContains;
551+ };
552+
553+ function add(q, value) {
554+ if (length > options.cacheLength){
555+ flush();
556+ }
557+ if (!data[q]){
558+ length++;
559+ }
560+ data[q] = value;
561+ }
562+
563+ function populate(){
564+ if( !options.data ) return false;
565+ // track the matches
566+ var stMatchSets = {},
567+ nullData = 0;
568+
569+ // no url was specified, we need to adjust the cache length to make sure it fits the local data store
570+ if( !options.url ) options.cacheLength = 1;
571+
572+ // track all options for minChars = 0
573+ stMatchSets[""] = [];
574+
575+ // loop through the array and create a lookup structure
576+ for ( var i = 0, ol = options.data.length; i < ol; i++ ) {
577+ var rawValue = options.data[i];
578+ // if rawValue is a string, make an array otherwise just reference the array
579+ rawValue = (typeof rawValue == "string") ? [rawValue] : rawValue;
580+
581+ var value = options.formatMatch(rawValue, i+1, options.data.length);
582+ if ( value === false )
583+ continue;
584+
585+ var firstChar = value.charAt(0).toLowerCase();
586+ // if no lookup array for this character exists, look it up now
587+ if( !stMatchSets[firstChar] )
588+ stMatchSets[firstChar] = [];
589+
590+ // if the match is a string
591+ var row = {
592+ value: value,
593+ data: rawValue,
594+ result: options.formatResult && options.formatResult(rawValue) || value
595+ };
596+
597+ // push the current match into the set list
598+ stMatchSets[firstChar].push(row);
599+
600+ // keep track of minChars zero items
601+ if ( nullData++ < options.max ) {
602+ stMatchSets[""].push(row);
603+ }
604+ };
605+
606+ // add the data items to the cache
607+ $.each(stMatchSets, function(i, value) {
608+ // increase the cache size
609+ options.cacheLength++;
610+ // add to the cache
611+ add(i, value);
612+ });
613+ }
614+
615+ // populate any existing data
616+ setTimeout(populate, 25);
617+
618+ function flush(){
619+ data = {};
620+ length = 0;
621+ }
622+
623+ return {
624+ flush: flush,
625+ add: add,
626+ populate: populate,
627+ load: function(q) {
628+ if (!options.cacheLength || !length)
629+ return null;
630+ /*
631+ * if dealing w/local data and matchContains than we must make sure
632+ * to loop through all the data collections looking for matches
633+ */
634+ if( !options.url && options.matchContains ){
635+ // track all matches
636+ var csub = [];
637+ // loop through all the data grids for matches
638+ for( var k in data ){
639+ // don't search through the stMatchSets[""] (minChars: 0) cache
640+ // this prevents duplicates
641+ if( k.length > 0 ){
642+ var c = data[k];
643+ $.each(c, function(i, x) {
644+ // if we've got a match, add it to the array
645+ if (matchSubset(x.value, q)) {
646+ csub.push(x);
647+ }
648+ });
649+ }
650+ }
651+ return csub;
652+ } else
653+ // if the exact item exists, use it
654+ if (data[q]){
655+ return data[q];
656+ } else
657+ if (options.matchSubset) {
658+ for (var i = q.length - 1; i >= options.minChars; i--) {
659+ var c = data[q.substr(0, i)];
660+ if (c) {
661+ var csub = [];
662+ $.each(c, function(i, x) {
663+ if (matchSubset(x.value, q)) {
664+ csub[csub.length] = x;
665+ }
666+ });
667+ return csub;
668+ }
669+ }
670+ }
671+ return null;
672+ }
673+ };
674+};
675+
676+$.Autocompleter.Select = function (options, input, select, config) {
677+ var CLASSES = {
678+ ACTIVE: "ac_over"
679+ };
680+
681+ var listItems,
682+ active = -1,
683+ data,
684+ term = "",
685+ needsInit = true,
686+ element,
687+ list;
688+
689+ // Create results
690+ function init() {
691+ if (!needsInit)
692+ return;
693+ element = $("<div/>")
694+ .hide()
695+ .addClass(options.resultsClass)
696+ .css("position", "absolute")
697+ .appendTo(document.body);
698+
699+ list = $("<ul/>").appendTo(element).mouseover( function(event) {
700+ if(target(event).nodeName && target(event).nodeName.toUpperCase() == 'LI') {
701+ active = $("li", list).removeClass(CLASSES.ACTIVE).index(target(event));
702+ $(target(event)).addClass(CLASSES.ACTIVE);
703+ }
704+ }).click(function(event) {
705+ $(target(event)).addClass(CLASSES.ACTIVE);
706+ select();
707+ // TODO provide option to avoid setting focus again after selection? useful for cleanup-on-focus
708+ input.focus();
709+ return false;
710+ }).mousedown(function() {
711+ config.mouseDownOnSelect = true;
712+ }).mouseup(function() {
713+ config.mouseDownOnSelect = false;
714+ });
715+
716+ if( options.width > 0 )
717+ element.css("width", options.width);
718+
719+ needsInit = false;
720+ }
721+
722+ function target(event) {
723+ var element = event.target;
724+ while(element && element.tagName != "LI")
725+ element = element.parentNode;
726+ // more fun with IE, sometimes event.target is empty, just ignore it then
727+ if(!element)
728+ return [];
729+ return element;
730+ }
731+
732+ function moveSelect(step) {
733+ listItems.slice(active, active + 1).removeClass(CLASSES.ACTIVE);
734+ movePosition(step);
735+ var activeItem = listItems.slice(active, active + 1).addClass(CLASSES.ACTIVE);
736+ if(options.scroll) {
737+ var offset = 0;
738+ listItems.slice(0, active).each(function() {
739+ offset += this.offsetHeight;
740+ });
741+ if((offset + activeItem[0].offsetHeight - list.scrollTop()) > list[0].clientHeight) {
742+ list.scrollTop(offset + activeItem[0].offsetHeight - list.innerHeight());
743+ } else if(offset < list.scrollTop()) {
744+ list.scrollTop(offset);
745+ }
746+ }
747+ };
748+
749+ function movePosition(step) {
750+ active += step;
751+ if (active < 0) {
752+ active = listItems.size() - 1;
753+ } else if (active >= listItems.size()) {
754+ active = 0;
755+ }
756+ }
757+
758+ function limitNumberOfItems(available) {
759+ return options.max && options.max < available
760+ ? options.max
761+ : available;
762+ }
763+
764+ function fillList() {
765+ list.empty();
766+ var max = limitNumberOfItems(data.length);
767+ for (var i=0; i < max; i++) {
768+ if (!data[i])
769+ continue;
770+ var formatted = options.formatItem(data[i].data, i+1, max, data[i].value, term);
771+ if ( formatted === false )
772+ continue;
773+ var li = $("<li/>").html( options.highlight(formatted, term) ).addClass(i%2 == 0 ? "ac_even" : "ac_odd").appendTo(list)[0];
774+ $.data(li, "ac_data", data[i]);
775+ }
776+ listItems = list.find("li");
777+ if ( options.selectFirst ) {
778+ listItems.slice(0, 1).addClass(CLASSES.ACTIVE);
779+ active = 0;
780+ }
781+ // apply bgiframe if available
782+ if ( $.fn.bgiframe )
783+ list.bgiframe();
784+ }
785+
786+ return {
787+ display: function(d, q) {
788+ init();
789+ data = d;
790+ term = q;
791+ fillList();
792+ },
793+ next: function() {
794+ moveSelect(1);
795+ },
796+ prev: function() {
797+ moveSelect(-1);
798+ },
799+ pageUp: function() {
800+ if (active != 0 && active - 8 < 0) {
801+ moveSelect( -active );
802+ } else {
803+ moveSelect(-8);
804+ }
805+ },
806+ pageDown: function() {
807+ if (active != listItems.size() - 1 && active + 8 > listItems.size()) {
808+ moveSelect( listItems.size() - 1 - active );
809+ } else {
810+ moveSelect(8);
811+ }
812+ },
813+ hide: function() {
814+ element && element.hide();
815+ listItems && listItems.removeClass(CLASSES.ACTIVE);
816+ active = -1;
817+ },
818+ visible : function() {
819+ return element && element.is(":visible");
820+ },
821+ current: function() {
822+ return this.visible() && (listItems.filter("." + CLASSES.ACTIVE)[0] || options.selectFirst && listItems[0]);
823+ },
824+ show: function() {
825+ var offset = $(input).offset();
826+ element.css({
827+ width: typeof options.width == "string" || options.width > 0 ? options.width : $(input).width(),
828+ top: offset.top + input.offsetHeight,
829+ left: offset.left
830+ }).show();
831+ if(options.scroll) {
832+ list.scrollTop(0);
833+ list.css({
834+ maxHeight: options.scrollHeight,
835+ overflow: 'auto'
836+ });
837+
838+ if($.browser.msie && typeof document.body.style.maxHeight === "undefined") {
839+ var listHeight = 0;
840+ listItems.each(function() {
841+ listHeight += this.offsetHeight;
842+ });
843+ var scrollbarsVisible = listHeight > options.scrollHeight;
844+ list.css('height', scrollbarsVisible ? options.scrollHeight : listHeight );
845+ if (!scrollbarsVisible) {
846+ // IE doesn't recalculate width when scrollbar disappears
847+ listItems.width( list.width() - parseInt(listItems.css("padding-left")) - parseInt(listItems.css("padding-right")) );
848+ }
849+ }
850+
851+ }
852+ },
853+ selected: function() {
854+ var selected = listItems && listItems.filter("." + CLASSES.ACTIVE).removeClass(CLASSES.ACTIVE);
855+ return selected && selected.length && $.data(selected[0], "ac_data");
856+ },
857+ emptyList: function (){
858+ list && list.empty();
859+ },
860+ unbind: function() {
861+ element && element.remove();
862+ }
863+ };
864+};
865+
866+$.Autocompleter.Selection = function(field, start, end) {
867+ if( field.createTextRange ){
868+ var selRange = field.createTextRange();
869+ selRange.collapse(true);
870+ selRange.moveStart("character", start);
871+ selRange.moveEnd("character", end);
872+ selRange.select();
873+ } else if( field.setSelectionRange ){
874+ field.setSelectionRange(start, end);
875+ } else {
876+ if( field.selectionStart ){
877+ field.selectionStart = start;
878+ field.selectionEnd = end;
879+ }
880+ }
881+ field.focus();
882+};
883+
884+})(jQuery);
885\ No newline at end of file
886
887=== added file 'webapp/content/js/jquery.flot.crosshair.js'
888--- webapp/content/js/jquery.flot.crosshair.js 1970-01-01 00:00:00 +0000
889+++ webapp/content/js/jquery.flot.crosshair.js 2010-11-01 18:48:50 +0000
890@@ -0,0 +1,156 @@
891+/*
892+Flot plugin for showing a crosshair, thin lines, when the mouse hovers
893+over the plot.
894+
895+ crosshair: {
896+ mode: null or "x" or "y" or "xy"
897+ color: color
898+ lineWidth: number
899+ }
900+
901+Set the mode to one of "x", "y" or "xy". The "x" mode enables a
902+vertical crosshair that lets you trace the values on the x axis, "y"
903+enables a horizontal crosshair and "xy" enables them both. "color" is
904+the color of the crosshair (default is "rgba(170, 0, 0, 0.80)"),
905+"lineWidth" is the width of the drawn lines (default is 1).
906+
907+The plugin also adds four public methods:
908+
909+ - setCrosshair(pos)
910+
911+ Set the position of the crosshair. Note that this is cleared if
912+ the user moves the mouse. "pos" should be on the form { x: xpos,
913+ y: ypos } (or x2 and y2 if you're using the secondary axes), which
914+ is coincidentally the same format as what you get from a "plothover"
915+ event. If "pos" is null, the crosshair is cleared.
916+
917+ - clearCrosshair()
918+
919+ Clear the crosshair.
920+
921+ - lockCrosshair(pos)
922+
923+ Cause the crosshair to lock to the current location, no longer
924+ updating if the user moves the mouse. Optionally supply a position
925+ (passed on to setCrosshair()) to move it to.
926+
927+ Example usage:
928+ var myFlot = $.plot( $("#graph"), ..., { crosshair: { mode: "x" } } };
929+ $("#graph").bind("plothover", function (evt, position, item) {
930+ if (item) {
931+ // Lock the crosshair to the data point being hovered
932+ myFlot.lockCrosshair({ x: item.datapoint[0], y: item.datapoint[1] });
933+ }
934+ else {
935+ // Return normal crosshair operation
936+ myFlot.unlockCrosshair();
937+ }
938+ });
939+
940+ - unlockCrosshair()
941+
942+ Free the crosshair to move again after locking it.
943+*/
944+
945+(function ($) {
946+ var options = {
947+ crosshair: {
948+ mode: null, // one of null, "x", "y" or "xy",
949+ color: "rgba(170, 0, 0, 0.80)",
950+ lineWidth: 1
951+ }
952+ };
953+
954+ function init(plot) {
955+ // position of crosshair in pixels
956+ var crosshair = { x: -1, y: -1, locked: false };
957+
958+ plot.setCrosshair = function setCrosshair(pos) {
959+ if (!pos)
960+ crosshair.x = -1;
961+ else {
962+ var axes = plot.getAxes();
963+
964+ crosshair.x = Math.max(0, Math.min(pos.x != null ? axes.xaxis.p2c(pos.x) : axes.x2axis.p2c(pos.x2), plot.width()));
965+ crosshair.y = Math.max(0, Math.min(pos.y != null ? axes.yaxis.p2c(pos.y) : axes.y2axis.p2c(pos.y2), plot.height()));
966+ }
967+
968+ plot.triggerRedrawOverlay();
969+ };
970+
971+ plot.clearCrosshair = plot.setCrosshair; // passes null for pos
972+
973+ plot.lockCrosshair = function lockCrosshair(pos) {
974+ if (pos)
975+ plot.setCrosshair(pos);
976+ crosshair.locked = true;
977+ }
978+
979+ plot.unlockCrosshair = function unlockCrosshair() {
980+ crosshair.locked = false;
981+ }
982+
983+ plot.hooks.bindEvents.push(function (plot, eventHolder) {
984+ if (!plot.getOptions().crosshair.mode)
985+ return;
986+
987+ eventHolder.mouseout(function () {
988+ if (crosshair.x != -1) {
989+ crosshair.x = -1;
990+ plot.triggerRedrawOverlay();
991+ }
992+ });
993+
994+ eventHolder.mousemove(function (e) {
995+ if (plot.getSelection && plot.getSelection()) {
996+ crosshair.x = -1; // hide the crosshair while selecting
997+ return;
998+ }
999+
1000+ if (crosshair.locked)
1001+ return;
1002+
1003+ var offset = plot.offset();
1004+ crosshair.x = Math.max(0, Math.min(e.pageX - offset.left, plot.width()));
1005+ crosshair.y = Math.max(0, Math.min(e.pageY - offset.top, plot.height()));
1006+ plot.triggerRedrawOverlay();
1007+ });
1008+ });
1009+
1010+ plot.hooks.drawOverlay.push(function (plot, ctx) {
1011+ var c = plot.getOptions().crosshair;
1012+ if (!c.mode)
1013+ return;
1014+
1015+ var plotOffset = plot.getPlotOffset();
1016+
1017+ ctx.save();
1018+ ctx.translate(plotOffset.left, plotOffset.top);
1019+
1020+ if (crosshair.x != -1) {
1021+ ctx.strokeStyle = c.color;
1022+ ctx.lineWidth = c.lineWidth;
1023+ ctx.lineJoin = "round";
1024+
1025+ ctx.beginPath();
1026+ if (c.mode.indexOf("x") != -1) {
1027+ ctx.moveTo(crosshair.x, 0);
1028+ ctx.lineTo(crosshair.x, plot.height());
1029+ }
1030+ if (c.mode.indexOf("y") != -1) {
1031+ ctx.moveTo(0, crosshair.y);
1032+ ctx.lineTo(plot.width(), crosshair.y);
1033+ }
1034+ ctx.stroke();
1035+ }
1036+ ctx.restore();
1037+ });
1038+ }
1039+
1040+ $.plot.plugins.push({
1041+ init: init,
1042+ options: options,
1043+ name: 'crosshair',
1044+ version: '1.0'
1045+ });
1046+})(jQuery);
1047
1048=== added file 'webapp/content/js/jquery.flot.js'
1049--- webapp/content/js/jquery.flot.js 1970-01-01 00:00:00 +0000
1050+++ webapp/content/js/jquery.flot.js 2010-11-01 18:48:50 +0000
1051@@ -0,0 +1,2119 @@
1052+/* Javascript plotting library for jQuery, v. 0.6.
1053+ *
1054+ * Released under the MIT license by IOLA, December 2007.
1055+ *
1056+ */
1057+
1058+// first an inline dependency, jquery.colorhelpers.js, we inline it here
1059+// for convenience
1060+
1061+/* Plugin for jQuery for working with colors.
1062+ *
1063+ * Version 1.0.
1064+ *
1065+ * Inspiration from jQuery color animation plugin by John Resig.
1066+ *
1067+ * Released under the MIT license by Ole Laursen, October 2009.
1068+ *
1069+ * Examples:
1070+ *
1071+ * $.color.parse("#fff").scale('rgb', 0.25).add('a', -0.5).toString()
1072+ * var c = $.color.extract($("#mydiv"), 'background-color');
1073+ * console.log(c.r, c.g, c.b, c.a);
1074+ * $.color.make(100, 50, 25, 0.4).toString() // returns "rgba(100,50,25,0.4)"
1075+ *
1076+ * Note that .scale() and .add() work in-place instead of returning
1077+ * new objects.
1078+ */
1079+(function(){jQuery.color={};jQuery.color.make=function(E,D,B,C){var F={};F.r=E||0;F.g=D||0;F.b=B||0;F.a=C!=null?C:1;F.add=function(I,H){for(var G=0;G<I.length;++G){F[I.charAt(G)]+=H}return F.normalize()};F.scale=function(I,H){for(var G=0;G<I.length;++G){F[I.charAt(G)]*=H}return F.normalize()};F.toString=function(){if(F.a>=1){return"rgb("+[F.r,F.g,F.b].join(",")+")"}else{return"rgba("+[F.r,F.g,F.b,F.a].join(",")+")"}};F.normalize=function(){function G(I,J,H){return J<I?I:(J>H?H:J)}F.r=G(0,parseInt(F.r),255);F.g=G(0,parseInt(F.g),255);F.b=G(0,parseInt(F.b),255);F.a=G(0,F.a,1);return F};F.clone=function(){return jQuery.color.make(F.r,F.b,F.g,F.a)};return F.normalize()};jQuery.color.extract=function(C,B){var D;do{D=C.css(B).toLowerCase();if(D!=""&&D!="transparent"){break}C=C.parent()}while(!jQuery.nodeName(C.get(0),"body"));if(D=="rgba(0, 0, 0, 0)"){D="transparent"}return jQuery.color.parse(D)};jQuery.color.parse=function(E){var D,B=jQuery.color.make;if(D=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(E)){return B(parseInt(D[1],10),parseInt(D[2],10),parseInt(D[3],10))}if(D=/rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(E)){return B(parseInt(D[1],10),parseInt(D[2],10),parseInt(D[3],10),parseFloat(D[4]))}if(D=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(E)){return B(parseFloat(D[1])*2.55,parseFloat(D[2])*2.55,parseFloat(D[3])*2.55)}if(D=/rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(E)){return B(parseFloat(D[1])*2.55,parseFloat(D[2])*2.55,parseFloat(D[3])*2.55,parseFloat(D[4]))}if(D=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(E)){return B(parseInt(D[1],16),parseInt(D[2],16),parseInt(D[3],16))}if(D=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(E)){return B(parseInt(D[1]+D[1],16),parseInt(D[2]+D[2],16),parseInt(D[3]+D[3],16))}var C=jQuery.trim(E).toLowerCase();if(C=="transparent"){return B(255,255,255,0)}else{D=A[C];return B(D[0],D[1],D[2])}};var A={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0]}})();
1080+
1081+// the actual Flot code
1082+(function($) {
1083+ function Plot(placeholder, data_, options_, plugins) {
1084+ // data is on the form:
1085+ // [ series1, series2 ... ]
1086+ // where series is either just the data as [ [x1, y1], [x2, y2], ... ]
1087+ // or { data: [ [x1, y1], [x2, y2], ... ], label: "some label", ... }
1088+
1089+ var series = [],
1090+ options = {
1091+ // the color theme used for graphs
1092+ colors: ["#edc240", "#afd8f8", "#cb4b4b", "#4da74d", "#9440ed"],
1093+ legend: {
1094+ show: true,
1095+ noColumns: 1, // number of colums in legend table
1096+ labelFormatter: null, // fn: string -> string
1097+ labelBoxBorderColor: "#ccc", // border color for the little label boxes
1098+ container: null, // container (as jQuery object) to put legend in, null means default on top of graph
1099+ position: "ne", // position of default legend container within plot
1100+ margin: 5, // distance from grid edge to default legend container within plot
1101+ backgroundColor: null, // null means auto-detect
1102+ backgroundOpacity: 0.85 // set to 0 to avoid background
1103+ },
1104+ xaxis: {
1105+ mode: null, // null or "time"
1106+ transform: null, // null or f: number -> number to transform axis
1107+ inverseTransform: null, // if transform is set, this should be the inverse function
1108+ min: null, // min. value to show, null means set automatically
1109+ max: null, // max. value to show, null means set automatically
1110+ autoscaleMargin: null, // margin in % to add if auto-setting min/max
1111+ ticks: null, // either [1, 3] or [[1, "a"], 3] or (fn: axis info -> ticks) or app. number of ticks for auto-ticks
1112+ tickFormatter: null, // fn: number -> string
1113+ labelWidth: null, // size of tick labels in pixels
1114+ labelHeight: null,
1115+
1116+ // mode specific options
1117+ tickDecimals: null, // no. of decimals, null means auto
1118+ tickSize: null, // number or [number, "unit"]
1119+ minTickSize: null, // number or [number, "unit"]
1120+ monthNames: null, // list of names of months
1121+ timeformat: null, // format string to use
1122+ twelveHourClock: false // 12 or 24 time in time mode
1123+ },
1124+ yaxis: {
1125+ autoscaleMargin: 0.02
1126+ },
1127+ x2axis: {
1128+ autoscaleMargin: null
1129+ },
1130+ y2axis: {
1131+ autoscaleMargin: 0.02
1132+ },
1133+ series: {
1134+ points: {
1135+ show: false,
1136+ radius: 3,
1137+ lineWidth: 2, // in pixels
1138+ fill: true,
1139+ fillColor: "#ffffff"
1140+ },
1141+ lines: {
1142+ // we don't put in show: false so we can see
1143+ // whether lines were actively disabled
1144+ lineWidth: 2, // in pixels
1145+ fill: false,
1146+ fillColor: null,
1147+ steps: false
1148+ },
1149+ bars: {
1150+ show: false,
1151+ lineWidth: 2, // in pixels
1152+ barWidth: 1, // in units of the x axis
1153+ fill: true,
1154+ fillColor: null,
1155+ align: "left", // or "center"
1156+ horizontal: false // when horizontal, left is now top
1157+ },
1158+ shadowSize: 3
1159+ },
1160+ grid: {
1161+ show: true,
1162+ aboveData: false,
1163+ color: "#545454", // primary color used for outline and labels
1164+ backgroundColor: null, // null for transparent, else color
1165+ tickColor: "rgba(0,0,0,0.15)", // color used for the ticks
1166+ labelMargin: 5, // in pixels
1167+ borderWidth: 2, // in pixels
1168+ borderColor: null, // set if different from the grid color
1169+ markings: null, // array of ranges or fn: axes -> array of ranges
1170+ markingsColor: "#f4f4f4",
1171+ markingsLineWidth: 2,
1172+ // interactive stuff
1173+ clickable: false,
1174+ hoverable: false,
1175+ autoHighlight: true, // highlight in case mouse is near
1176+ mouseActiveRadius: 10 // how far the mouse can be away to activate an item
1177+ },
1178+ hooks: {}
1179+ },
1180+ canvas = null, // the canvas for the plot itself
1181+ overlay = null, // canvas for interactive stuff on top of plot
1182+ eventHolder = null, // jQuery object that events should be bound to
1183+ ctx = null, octx = null,
1184+ axes = { xaxis: {}, yaxis: {}, x2axis: {}, y2axis: {} },
1185+ plotOffset = { left: 0, right: 0, top: 0, bottom: 0},
1186+ canvasWidth = 0, canvasHeight = 0,
1187+ plotWidth = 0, plotHeight = 0,
1188+ hooks = {
1189+ processOptions: [],
1190+ processRawData: [],
1191+ processDatapoints: [],
1192+ draw: [],
1193+ bindEvents: [],
1194+ drawOverlay: []
1195+ },
1196+ plot = this;
1197+
1198+ // public functions
1199+ plot.setData = setData;
1200+ plot.setupGrid = setupGrid;
1201+ plot.draw = draw;
1202+ plot.getPlaceholder = function() { return placeholder; };
1203+ plot.getCanvas = function() { return canvas; };
1204+ plot.getPlotOffset = function() { return plotOffset; };
1205+ plot.width = function () { return plotWidth; };
1206+ plot.height = function () { return plotHeight; };
1207+ plot.offset = function () {
1208+ var o = eventHolder.offset();
1209+ o.left += plotOffset.left;
1210+ o.top += plotOffset.top;
1211+ return o;
1212+ };
1213+ plot.getData = function() { return series; };
1214+ plot.getAxes = function() { return axes; };
1215+ plot.getOptions = function() { return options; };
1216+ plot.highlight = highlight;
1217+ plot.unhighlight = unhighlight;
1218+ plot.triggerRedrawOverlay = triggerRedrawOverlay;
1219+ plot.pointOffset = function(point) {
1220+ return { left: parseInt(axisSpecToRealAxis(point, "xaxis").p2c(+point.x) + plotOffset.left),
1221+ top: parseInt(axisSpecToRealAxis(point, "yaxis").p2c(+point.y) + plotOffset.top) };
1222+ };
1223+
1224+
1225+ // public attributes
1226+ plot.hooks = hooks;
1227+
1228+ // initialize
1229+ initPlugins(plot);
1230+ parseOptions(options_);
1231+ constructCanvas();
1232+ setData(data_);
1233+ setupGrid();
1234+ draw();
1235+ bindEvents();
1236+
1237+
1238+ function executeHooks(hook, args) {
1239+ args = [plot].concat(args);
1240+ for (var i = 0; i < hook.length; ++i)
1241+ hook[i].apply(this, args);
1242+ }
1243+
1244+ function initPlugins() {
1245+ for (var i = 0; i < plugins.length; ++i) {
1246+ var p = plugins[i];
1247+ p.init(plot);
1248+ if (p.options)
1249+ $.extend(true, options, p.options);
1250+ }
1251+ }
1252+
1253+ function parseOptions(opts) {
1254+ $.extend(true, options, opts);
1255+ if (options.grid.borderColor == null)
1256+ options.grid.borderColor = options.grid.color;
1257+ // backwards compatibility, to be removed in future
1258+ if (options.xaxis.noTicks && options.xaxis.ticks == null)
1259+ options.xaxis.ticks = options.xaxis.noTicks;
1260+ if (options.yaxis.noTicks && options.yaxis.ticks == null)
1261+ options.yaxis.ticks = options.yaxis.noTicks;
1262+ if (options.grid.coloredAreas)
1263+ options.grid.markings = options.grid.coloredAreas;
1264+ if (options.grid.coloredAreasColor)
1265+ options.grid.markingsColor = options.grid.coloredAreasColor;
1266+ if (options.lines)
1267+ $.extend(true, options.series.lines, options.lines);
1268+ if (options.points)
1269+ $.extend(true, options.series.points, options.points);
1270+ if (options.bars)
1271+ $.extend(true, options.series.bars, options.bars);
1272+ if (options.shadowSize)
1273+ options.series.shadowSize = options.shadowSize;
1274+
1275+ for (var n in hooks)
1276+ if (options.hooks[n] && options.hooks[n].length)
1277+ hooks[n] = hooks[n].concat(options.hooks[n]);
1278+
1279+ executeHooks(hooks.processOptions, [options]);
1280+ }
1281+
1282+ function setData(d) {
1283+ series = parseData(d);
1284+ fillInSeriesOptions();
1285+ processData();
1286+ }
1287+
1288+ function parseData(d) {
1289+ var res = [];
1290+ for (var i = 0; i < d.length; ++i) {
1291+ var s = $.extend(true, {}, options.series);
1292+
1293+ if (d[i].data) {
1294+ s.data = d[i].data; // move the data instead of deep-copy
1295+ delete d[i].data;
1296+
1297+ $.extend(true, s, d[i]);
1298+
1299+ d[i].data = s.data;
1300+ }
1301+ else
1302+ s.data = d[i];
1303+ res.push(s);
1304+ }
1305+
1306+ return res;
1307+ }
1308+
1309+ function axisSpecToRealAxis(obj, attr) {
1310+ var a = obj[attr];
1311+ if (!a || a == 1)
1312+ return axes[attr];
1313+ if (typeof a == "number")
1314+ return axes[attr.charAt(0) + a + attr.slice(1)];
1315+ return a; // assume it's OK
1316+ }
1317+
1318+ function fillInSeriesOptions() {
1319+ var i;
1320+
1321+ // collect what we already got of colors
1322+ var neededColors = series.length,
1323+ usedColors = [],
1324+ assignedColors = [];
1325+ for (i = 0; i < series.length; ++i) {
1326+ var sc = series[i].color;
1327+ if (sc != null) {
1328+ --neededColors;
1329+ if (typeof sc == "number")
1330+ assignedColors.push(sc);
1331+ else
1332+ usedColors.push($.color.parse(series[i].color));
1333+ }
1334+ }
1335+
1336+ // we might need to generate more colors if higher indices
1337+ // are assigned
1338+ for (i = 0; i < assignedColors.length; ++i) {
1339+ neededColors = Math.max(neededColors, assignedColors[i] + 1);
1340+ }
1341+
1342+ // produce colors as needed
1343+ var colors = [], variation = 0;
1344+ i = 0;
1345+ while (colors.length < neededColors) {
1346+ var c;
1347+ if (options.colors.length == i) // check degenerate case
1348+ c = $.color.make(100, 100, 100);
1349+ else
1350+ c = $.color.parse(options.colors[i]);
1351+
1352+ // vary color if needed
1353+ var sign = variation % 2 == 1 ? -1 : 1;
1354+ c.scale('rgb', 1 + sign * Math.ceil(variation / 2) * 0.2)
1355+
1356+ // FIXME: if we're getting to close to something else,
1357+ // we should probably skip this one
1358+ colors.push(c);
1359+
1360+ ++i;
1361+ if (i >= options.colors.length) {
1362+ i = 0;
1363+ ++variation;
1364+ }
1365+ }
1366+
1367+ // fill in the options
1368+ var colori = 0, s;
1369+ for (i = 0; i < series.length; ++i) {
1370+ s = series[i];
1371+
1372+ // assign colors
1373+ if (s.color == null) {
1374+ s.color = colors[colori].toString();
1375+ ++colori;
1376+ }
1377+ else if (typeof s.color == "number")
1378+ s.color = colors[s.color].toString();
1379+
1380+ // turn on lines automatically in case nothing is set
1381+ if (s.lines.show == null) {
1382+ var v, show = true;
1383+ for (v in s)
1384+ if (s[v].show) {
1385+ show = false;
1386+ break;
1387+ }
1388+ if (show)
1389+ s.lines.show = true;
1390+ }
1391+
1392+ // setup axes
1393+ s.xaxis = axisSpecToRealAxis(s, "xaxis");
1394+ s.yaxis = axisSpecToRealAxis(s, "yaxis");
1395+ }
1396+ }
1397+
1398+ function processData() {
1399+ var topSentry = Number.POSITIVE_INFINITY,
1400+ bottomSentry = Number.NEGATIVE_INFINITY,
1401+ i, j, k, m, length,
1402+ s, points, ps, x, y, axis, val, f, p;
1403+
1404+ for (axis in axes) {
1405+ axes[axis].datamin = topSentry;
1406+ axes[axis].datamax = bottomSentry;
1407+ axes[axis].used = false;
1408+ }
1409+
1410+ function updateAxis(axis, min, max) {
1411+ if (min < axis.datamin)
1412+ axis.datamin = min;
1413+ if (max > axis.datamax)
1414+ axis.datamax = max;
1415+ }
1416+
1417+ for (i = 0; i < series.length; ++i) {
1418+ s = series[i];
1419+ s.datapoints = { points: [] };
1420+
1421+ executeHooks(hooks.processRawData, [ s, s.data, s.datapoints ]);
1422+ }
1423+
1424+ // first pass: clean and copy data
1425+ for (i = 0; i < series.length; ++i) {
1426+ s = series[i];
1427+
1428+ var data = s.data, format = s.datapoints.format;
1429+
1430+ if (!format) {
1431+ format = [];
1432+ // find out how to copy
1433+ format.push({ x: true, number: true, required: true });
1434+ format.push({ y: true, number: true, required: true });
1435+
1436+ if (s.bars.show)
1437+ format.push({ y: true, number: true, required: false, defaultValue: 0 });
1438+
1439+ s.datapoints.format = format;
1440+ }
1441+
1442+ if (s.datapoints.pointsize != null)
1443+ continue; // already filled in
1444+
1445+ if (s.datapoints.pointsize == null)
1446+ s.datapoints.pointsize = format.length;
1447+
1448+ ps = s.datapoints.pointsize;
1449+ points = s.datapoints.points;
1450+
1451+ insertSteps = s.lines.show && s.lines.steps;
1452+ s.xaxis.used = s.yaxis.used = true;
1453+
1454+ for (j = k = 0; j < data.length; ++j, k += ps) {
1455+ p = data[j];
1456+
1457+ var nullify = p == null;
1458+ if (!nullify) {
1459+ for (m = 0; m < ps; ++m) {
1460+ val = p[m];
1461+ f = format[m];
1462+
1463+ if (f) {
1464+ if (f.number && val != null) {
1465+ val = +val; // convert to number
1466+ if (isNaN(val))
1467+ val = null;
1468+ }
1469+
1470+ if (val == null) {
1471+ if (f.required)
1472+ nullify = true;
1473+
1474+ if (f.defaultValue != null)
1475+ val = f.defaultValue;
1476+ }
1477+ }
1478+
1479+ points[k + m] = val;
1480+ }
1481+ }
1482+
1483+ if (nullify) {
1484+ for (m = 0; m < ps; ++m) {
1485+ val = points[k + m];
1486+ if (val != null) {
1487+ f = format[m];
1488+ // extract min/max info
1489+ if (f.x)
1490+ updateAxis(s.xaxis, val, val);
1491+ if (f.y)
1492+ updateAxis(s.yaxis, val, val);
1493+ }
1494+ points[k + m] = null;
1495+ }
1496+ }
1497+ else {
1498+ // a little bit of line specific stuff that
1499+ // perhaps shouldn't be here, but lacking
1500+ // better means...
1501+ if (insertSteps && k > 0
1502+ && points[k - ps] != null
1503+ && points[k - ps] != points[k]
1504+ && points[k - ps + 1] != points[k + 1]) {
1505+ // copy the point to make room for a middle point
1506+ for (m = 0; m < ps; ++m)
1507+ points[k + ps + m] = points[k + m];
1508+
1509+ // middle point has same y
1510+ points[k + 1] = points[k - ps + 1];
1511+
1512+ // we've added a point, better reflect that
1513+ k += ps;
1514+ }
1515+ }
1516+ }
1517+ }
1518+
1519+ // give the hooks a chance to run
1520+ for (i = 0; i < series.length; ++i) {
1521+ s = series[i];
1522+
1523+ executeHooks(hooks.processDatapoints, [ s, s.datapoints]);
1524+ }
1525+
1526+ // second pass: find datamax/datamin for auto-scaling
1527+ for (i = 0; i < series.length; ++i) {
1528+ s = series[i];
1529+ points = s.datapoints.points,
1530+ ps = s.datapoints.pointsize;
1531+
1532+ var xmin = topSentry, ymin = topSentry,
1533+ xmax = bottomSentry, ymax = bottomSentry;
1534+
1535+ for (j = 0; j < points.length; j += ps) {
1536+ if (points[j] == null)
1537+ continue;
1538+
1539+ for (m = 0; m < ps; ++m) {
1540+ val = points[j + m];
1541+ f = format[m];
1542+ if (!f)
1543+ continue;
1544+
1545+ if (f.x) {
1546+ if (val < xmin)
1547+ xmin = val;
1548+ if (val > xmax)
1549+ xmax = val;
1550+ }
1551+ if (f.y) {
1552+ if (val < ymin)
1553+ ymin = val;
1554+ if (val > ymax)
1555+ ymax = val;
1556+ }
1557+ }
1558+ }
1559+
1560+ if (s.bars.show) {
1561+ // make sure we got room for the bar on the dancing floor
1562+ var delta = s.bars.align == "left" ? 0 : -s.bars.barWidth/2;
1563+ if (s.bars.horizontal) {
1564+ ymin += delta;
1565+ ymax += delta + s.bars.barWidth;
1566+ }
1567+ else {
1568+ xmin += delta;
1569+ xmax += delta + s.bars.barWidth;
1570+ }
1571+ }
1572+
1573+ updateAxis(s.xaxis, xmin, xmax);
1574+ updateAxis(s.yaxis, ymin, ymax);
1575+ }
1576+
1577+ for (axis in axes) {
1578+ if (axes[axis].datamin == topSentry)
1579+ axes[axis].datamin = null;
1580+ if (axes[axis].datamax == bottomSentry)
1581+ axes[axis].datamax = null;
1582+ }
1583+ }
1584+
1585+ function constructCanvas() {
1586+ function makeCanvas(width, height) {
1587+ var c = document.createElement('canvas');
1588+ c.width = width;
1589+ c.height = height;
1590+ if ($.browser.msie) // excanvas hack
1591+ c = window.G_vmlCanvasManager.initElement(c);
1592+ return c;
1593+ }
1594+
1595+ canvasWidth = placeholder.width();
1596+ canvasHeight = placeholder.height();
1597+ placeholder.html(""); // clear placeholder
1598+ if (placeholder.css("position") == 'static')
1599+ placeholder.css("position", "relative"); // for positioning labels and overlay
1600+
1601+ if (canvasWidth <= 0 || canvasHeight <= 0)
1602+ throw "Invalid dimensions for plot, width = " + canvasWidth + ", height = " + canvasHeight;
1603+
1604+ if ($.browser.msie) // excanvas hack
1605+ window.G_vmlCanvasManager.init_(document); // make sure everything is setup
1606+
1607+ // the canvas
1608+ canvas = $(makeCanvas(canvasWidth, canvasHeight)).appendTo(placeholder).get(0);
1609+ ctx = canvas.getContext("2d");
1610+
1611+ // overlay canvas for interactive features
1612+ overlay = $(makeCanvas(canvasWidth, canvasHeight)).css({ position: 'absolute', left: 0, top: 0 }).appendTo(placeholder).get(0);
1613+ octx = overlay.getContext("2d");
1614+ octx.stroke();
1615+ }
1616+
1617+ function bindEvents() {
1618+ // we include the canvas in the event holder too, because IE 7
1619+ // sometimes has trouble with the stacking order
1620+ eventHolder = $([overlay, canvas]);
1621+
1622+ // bind events
1623+ if (options.grid.hoverable)
1624+ eventHolder.mousemove(onMouseMove);
1625+
1626+ if (options.grid.clickable)
1627+ eventHolder.click(onClick);
1628+
1629+ executeHooks(hooks.bindEvents, [eventHolder]);
1630+ }
1631+
1632+ function setupGrid() {
1633+ function setTransformationHelpers(axis, o) {
1634+ function identity(x) { return x; }
1635+
1636+ var s, m, t = o.transform || identity,
1637+ it = o.inverseTransform;
1638+
1639+ // add transformation helpers
1640+ if (axis == axes.xaxis || axis == axes.x2axis) {
1641+ // precompute how much the axis is scaling a point
1642+ // in canvas space
1643+ s = axis.scale = plotWidth / (t(axis.max) - t(axis.min));
1644+ m = t(axis.min);
1645+
1646+ // data point to canvas coordinate
1647+ if (t == identity) // slight optimization
1648+ axis.p2c = function (p) { return (p - m) * s; };
1649+ else
1650+ axis.p2c = function (p) { return (t(p) - m) * s; };
1651+ // canvas coordinate to data point
1652+ if (!it)
1653+ axis.c2p = function (c) { return m + c / s; };
1654+ else
1655+ axis.c2p = function (c) { return it(m + c / s); };
1656+ }
1657+ else {
1658+ s = axis.scale = plotHeight / (t(axis.max) - t(axis.min));
1659+ m = t(axis.max);
1660+
1661+ if (t == identity)
1662+ axis.p2c = function (p) { return (m - p) * s; };
1663+ else
1664+ axis.p2c = function (p) { return (m - t(p)) * s; };
1665+ if (!it)
1666+ axis.c2p = function (c) { return m - c / s; };
1667+ else
1668+ axis.c2p = function (c) { return it(m - c / s); };
1669+ }
1670+ }
1671+
1672+ function measureLabels(axis, axisOptions) {
1673+ var i, labels = [], l;
1674+
1675+ axis.labelWidth = axisOptions.labelWidth;
1676+ axis.labelHeight = axisOptions.labelHeight;
1677+
1678+ if (axis == axes.xaxis || axis == axes.x2axis) {
1679+ // to avoid measuring the widths of the labels, we
1680+ // construct fixed-size boxes and put the labels inside
1681+ // them, we don't need the exact figures and the
1682+ // fixed-size box content is easy to center
1683+ if (axis.labelWidth == null)
1684+ axis.labelWidth = canvasWidth / (axis.ticks.length > 0 ? axis.ticks.length : 1);
1685+
1686+ // measure x label heights
1687+ if (axis.labelHeight == null) {
1688+ labels = [];
1689+ for (i = 0; i < axis.ticks.length; ++i) {
1690+ l = axis.ticks[i].label;
1691+ if (l)
1692+ labels.push('<div class="tickLabel" style="float:left;width:' + axis.labelWidth + 'px">' + l + '</div>');
1693+ }
1694+
1695+ if (labels.length > 0) {
1696+ var dummyDiv = $('<div style="position:absolute;top:-10000px;width:10000px;font-size:smaller">'
1697+ + labels.join("") + '<div style="clear:left"></div></div>').appendTo(placeholder);
1698+ axis.labelHeight = dummyDiv.height();
1699+ dummyDiv.remove();
1700+ }
1701+ }
1702+ }
1703+ else if (axis.labelWidth == null || axis.labelHeight == null) {
1704+ // calculate y label dimensions
1705+ for (i = 0; i < axis.ticks.length; ++i) {
1706+ l = axis.ticks[i].label;
1707+ if (l)
1708+ labels.push('<div class="tickLabel">' + l + '</div>');
1709+ }
1710+
1711+ if (labels.length > 0) {
1712+ var dummyDiv = $('<div style="position:absolute;top:-10000px;font-size:smaller">'
1713+ + labels.join("") + '</div>').appendTo(placeholder);
1714+ if (axis.labelWidth == null)
1715+ axis.labelWidth = dummyDiv.width();
1716+ if (axis.labelHeight == null)
1717+ axis.labelHeight = dummyDiv.find("div").height();
1718+ dummyDiv.remove();
1719+ }
1720+
1721+ }
1722+
1723+ if (axis.labelWidth == null)
1724+ axis.labelWidth = 0;
1725+ if (axis.labelHeight == null)
1726+ axis.labelHeight = 0;
1727+ }
1728+
1729+ function setGridSpacing() {
1730+ // get the most space needed around the grid for things
1731+ // that may stick out
1732+ var maxOutset = options.grid.borderWidth;
1733+ for (i = 0; i < series.length; ++i)
1734+ maxOutset = Math.max(maxOutset, 2 * (series[i].points.radius + series[i].points.lineWidth/2));
1735+
1736+ plotOffset.left = plotOffset.right = plotOffset.top = plotOffset.bottom = maxOutset;
1737+
1738+ var margin = options.grid.labelMargin + options.grid.borderWidth;
1739+
1740+ if (axes.xaxis.labelHeight > 0)
1741+ plotOffset.bottom = Math.max(maxOutset, axes.xaxis.labelHeight + margin);
1742+ if (axes.yaxis.labelWidth > 0)
1743+ plotOffset.left = Math.max(maxOutset, axes.yaxis.labelWidth + margin);
1744+ if (axes.x2axis.labelHeight > 0)
1745+ plotOffset.top = Math.max(maxOutset, axes.x2axis.labelHeight + margin);
1746+ if (axes.y2axis.labelWidth > 0)
1747+ plotOffset.right = Math.max(maxOutset, axes.y2axis.labelWidth + margin);
1748+
1749+ plotWidth = canvasWidth - plotOffset.left - plotOffset.right;
1750+ plotHeight = canvasHeight - plotOffset.bottom - plotOffset.top;
1751+ }
1752+
1753+ var axis;
1754+ for (axis in axes)
1755+ setRange(axes[axis], options[axis]);
1756+
1757+ if (options.grid.show) {
1758+ for (axis in axes) {
1759+ prepareTickGeneration(axes[axis], options[axis]);
1760+ setTicks(axes[axis], options[axis]);
1761+ measureLabels(axes[axis], options[axis]);
1762+ }
1763+
1764+ setGridSpacing();
1765+ }
1766+ else {
1767+ plotOffset.left = plotOffset.right = plotOffset.top = plotOffset.bottom = 0;
1768+ plotWidth = canvasWidth;
1769+ plotHeight = canvasHeight;
1770+ }
1771+
1772+ for (axis in axes)
1773+ setTransformationHelpers(axes[axis], options[axis]);
1774+
1775+ if (options.grid.show)
1776+ insertLabels();
1777+
1778+ insertLegend();
1779+ }
1780+
1781+ function setRange(axis, axisOptions) {
1782+ var min = +(axisOptions.min != null ? axisOptions.min : axis.datamin),
1783+ max = +(axisOptions.max != null ? axisOptions.max : axis.datamax),
1784+ delta = max - min;
1785+
1786+ if (delta == 0.0) {
1787+ // degenerate case
1788+ var widen = max == 0 ? 1 : 0.01;
1789+
1790+ if (axisOptions.min == null)
1791+ min -= widen;
1792+ // alway widen max if we couldn't widen min to ensure we
1793+ // don't fall into min == max which doesn't work
1794+ if (axisOptions.max == null || axisOptions.min != null)
1795+ max += widen;
1796+ }
1797+ else {
1798+ // consider autoscaling
1799+ var margin = axisOptions.autoscaleMargin;
1800+ if (margin != null) {
1801+ if (axisOptions.min == null) {
1802+ min -= delta * margin;
1803+ // make sure we don't go below zero if all values
1804+ // are positive
1805+ if (min < 0 && axis.datamin != null && axis.datamin >= 0)
1806+ min = 0;
1807+ }
1808+ if (axisOptions.max == null) {
1809+ max += delta * margin;
1810+ if (max > 0 && axis.datamax != null && axis.datamax <= 0)
1811+ max = 0;
1812+ }
1813+ }
1814+ }
1815+ axis.min = min;
1816+ axis.max = max;
1817+ }
1818+
1819+ function prepareTickGeneration(axis, axisOptions) {
1820+ // estimate number of ticks
1821+ var noTicks;
1822+ if (typeof axisOptions.ticks == "number" && axisOptions.ticks > 0)
1823+ noTicks = axisOptions.ticks;
1824+ else if (axis == axes.xaxis || axis == axes.x2axis)
1825+ // heuristic based on the model a*sqrt(x) fitted to
1826+ // some reasonable data points
1827+ noTicks = 0.3 * Math.sqrt(canvasWidth);
1828+ else
1829+ noTicks = 0.3 * Math.sqrt(canvasHeight);
1830+
1831+ var delta = (axis.max - axis.min) / noTicks,
1832+ size, generator, unit, formatter, i, magn, norm;
1833+
1834+ if (axisOptions.mode == "time") {
1835+ // pretty handling of time
1836+
1837+ // map of app. size of time units in milliseconds
1838+ var timeUnitSize = {
1839+ "second": 1000,
1840+ "minute": 60 * 1000,
1841+ "hour": 60 * 60 * 1000,
1842+ "day": 24 * 60 * 60 * 1000,
1843+ "month": 30 * 24 * 60 * 60 * 1000,
1844+ "year": 365.2425 * 24 * 60 * 60 * 1000
1845+ };
1846+
1847+
1848+ // the allowed tick sizes, after 1 year we use
1849+ // an integer algorithm
1850+ var spec = [
1851+ [1, "second"], [2, "second"], [5, "second"], [10, "second"],
1852+ [30, "second"],
1853+ [1, "minute"], [2, "minute"], [5, "minute"], [10, "minute"],
1854+ [30, "minute"],
1855+ [1, "hour"], [2, "hour"], [4, "hour"],
1856+ [8, "hour"], [12, "hour"],
1857+ [1, "day"], [2, "day"], [3, "day"],
1858+ [0.25, "month"], [0.5, "month"], [1, "month"],
1859+ [2, "month"], [3, "month"], [6, "month"],
1860+ [1, "year"]
1861+ ];
1862+
1863+ var minSize = 0;
1864+ if (axisOptions.minTickSize != null) {
1865+ if (typeof axisOptions.tickSize == "number")
1866+ minSize = axisOptions.tickSize;
1867+ else
1868+ minSize = axisOptions.minTickSize[0] * timeUnitSize[axisOptions.minTickSize[1]];
1869+ }
1870+
1871+ for (i = 0; i < spec.length - 1; ++i)
1872+ if (delta < (spec[i][0] * timeUnitSize[spec[i][1]]
1873+ + spec[i + 1][0] * timeUnitSize[spec[i + 1][1]]) / 2
1874+ && spec[i][0] * timeUnitSize[spec[i][1]] >= minSize)
1875+ break;
1876+ size = spec[i][0];
1877+ unit = spec[i][1];
1878+
1879+ // special-case the possibility of several years
1880+ if (unit == "year") {
1881+ magn = Math.pow(10, Math.floor(Math.log(delta / timeUnitSize.year) / Math.LN10));
1882+ norm = (delta / timeUnitSize.year) / magn;
1883+ if (norm < 1.5)
1884+ size = 1;
1885+ else if (norm < 3)
1886+ size = 2;
1887+ else if (norm < 7.5)
1888+ size = 5;
1889+ else
1890+ size = 10;
1891+
1892+ size *= magn;
1893+ }
1894+
1895+ if (axisOptions.tickSize) {
1896+ size = axisOptions.tickSize[0];
1897+ unit = axisOptions.tickSize[1];
1898+ }
1899+
1900+ generator = function(axis) {
1901+ var ticks = [],
1902+ tickSize = axis.tickSize[0], unit = axis.tickSize[1],
1903+ d = new Date(axis.min);
1904+
1905+ var step = tickSize * timeUnitSize[unit];
1906+
1907+ if (unit == "second")
1908+ d.setUTCSeconds(floorInBase(d.getUTCSeconds(), tickSize));
1909+ if (unit == "minute")
1910+ d.setUTCMinutes(floorInBase(d.getUTCMinutes(), tickSize));
1911+ if (unit == "hour")
1912+ d.setUTCHours(floorInBase(d.getUTCHours(), tickSize));
1913+ if (unit == "month")
1914+ d.setUTCMonth(floorInBase(d.getUTCMonth(), tickSize));
1915+ if (unit == "year")
1916+ d.setUTCFullYear(floorInBase(d.getUTCFullYear(), tickSize));
1917+
1918+ // reset smaller components
1919+ d.setUTCMilliseconds(0);
1920+ if (step >= timeUnitSize.minute)
1921+ d.setUTCSeconds(0);
1922+ if (step >= timeUnitSize.hour)
1923+ d.setUTCMinutes(0);
1924+ if (step >= timeUnitSize.day)
1925+ d.setUTCHours(0);
1926+ if (step >= timeUnitSize.day * 4)
1927+ d.setUTCDate(1);
1928+ if (step >= timeUnitSize.year)
1929+ d.setUTCMonth(0);
1930+
1931+
1932+ var carry = 0, v = Number.NaN, prev;
1933+ do {
1934+ prev = v;
1935+ v = d.getTime();
1936+ ticks.push({ v: v, label: axis.tickFormatter(v, axis) });
1937+ if (unit == "month") {
1938+ if (tickSize < 1) {
1939+ // a bit complicated - we'll divide the month
1940+ // up but we need to take care of fractions
1941+ // so we don't end up in the middle of a day
1942+ d.setUTCDate(1);
1943+ var start = d.getTime();
1944+ d.setUTCMonth(d.getUTCMonth() + 1);
1945+ var end = d.getTime();
1946+ d.setTime(v + carry * timeUnitSize.hour + (end - start) * tickSize);
1947+ carry = d.getUTCHours();
1948+ d.setUTCHours(0);
1949+ }
1950+ else
1951+ d.setUTCMonth(d.getUTCMonth() + tickSize);
1952+ }
1953+ else if (unit == "year") {
1954+ d.setUTCFullYear(d.getUTCFullYear() + tickSize);
1955+ }
1956+ else
1957+ d.setTime(v + step);
1958+ } while (v < axis.max && v != prev);
1959+
1960+ return ticks;
1961+ };
1962+
1963+ formatter = function (v, axis) {
1964+ var d = new Date(v);
1965+
1966+ // first check global format
1967+ if (axisOptions.timeformat != null)
1968+ return $.plot.formatDate(d, axisOptions.timeformat, axisOptions.monthNames);
1969+
1970+ var t = axis.tickSize[0] * timeUnitSize[axis.tickSize[1]];
1971+ var span = axis.max - axis.min;
1972+ var suffix = (axisOptions.twelveHourClock) ? " %p" : "";
1973+
1974+ if (t < timeUnitSize.minute)
1975+ fmt = "%h:%M:%S" + suffix;
1976+ else if (t < timeUnitSize.day) {
1977+ if (span < 2 * timeUnitSize.day)
1978+ fmt = "%h:%M" + suffix;
1979+ else
1980+ fmt = "%b %d %h:%M" + suffix;
1981+ }
1982+ else if (t < timeUnitSize.month)
1983+ fmt = "%b %d";
1984+ else if (t < timeUnitSize.year) {
1985+ if (span < timeUnitSize.year)
1986+ fmt = "%b";
1987+ else
1988+ fmt = "%b %y";
1989+ }
1990+ else
1991+ fmt = "%y";
1992+
1993+ return $.plot.formatDate(d, fmt, axisOptions.monthNames);
1994+ };
1995+ }
1996+ else {
1997+ // pretty rounding of base-10 numbers
1998+ var maxDec = axisOptions.tickDecimals;
1999+ var dec = -Math.floor(Math.log(delta) / Math.LN10);
2000+ if (maxDec != null && dec > maxDec)
2001+ dec = maxDec;
2002+
2003+ magn = Math.pow(10, -dec);
2004+ norm = delta / magn; // norm is between 1.0 and 10.0
2005+
2006+ if (norm < 1.5)
2007+ size = 1;
2008+ else if (norm < 3) {
2009+ size = 2;
2010+ // special case for 2.5, requires an extra decimal
2011+ if (norm > 2.25 && (maxDec == null || dec + 1 <= maxDec)) {
2012+ size = 2.5;
2013+ ++dec;
2014+ }
2015+ }
2016+ else if (norm < 7.5)
2017+ size = 5;
2018+ else
2019+ size = 10;
2020+
2021+ size *= magn;
2022+
2023+ if (axisOptions.minTickSize != null && size < axisOptions.minTickSize)
2024+ size = axisOptions.minTickSize;
2025+
2026+ if (axisOptions.tickSize != null)
2027+ size = axisOptions.tickSize;
2028+
2029+ axis.tickDecimals = Math.max(0, (maxDec != null) ? maxDec : dec);
2030+
2031+ generator = function (axis) {
2032+ var ticks = [];
2033+
2034+ // spew out all possible ticks
2035+ var start = floorInBase(axis.min, axis.tickSize),
2036+ i = 0, v = Number.NaN, prev;
2037+ do {
2038+ prev = v;
2039+ v = start + i * axis.tickSize;
2040+ ticks.push({ v: v, label: axis.tickFormatter(v, axis) });
2041+ ++i;
2042+ } while (v < axis.max && v != prev);
2043+ return ticks;
2044+ };
2045+
2046+ formatter = function (v, axis) {
2047+ return v.toFixed(axis.tickDecimals);
2048+ };
2049+ }
2050+
2051+ axis.tickSize = unit ? [size, unit] : size;
2052+ axis.tickGenerator = generator;
2053+ if ($.isFunction(axisOptions.tickFormatter))
2054+ axis.tickFormatter = function (v, axis) { return "" + axisOptions.tickFormatter(v, axis); };
2055+ else
2056+ axis.tickFormatter = formatter;
2057+ }
2058+
2059+ function setTicks(axis, axisOptions) {
2060+ axis.ticks = [];
2061+
2062+ if (!axis.used)
2063+ return;
2064+
2065+ if (axisOptions.ticks == null)
2066+ axis.ticks = axis.tickGenerator(axis);
2067+ else if (typeof axisOptions.ticks == "number") {
2068+ if (axisOptions.ticks > 0)
2069+ axis.ticks = axis.tickGenerator(axis);
2070+ }
2071+ else if (axisOptions.ticks) {
2072+ var ticks = axisOptions.ticks;
2073+
2074+ if ($.isFunction(ticks))
2075+ // generate the ticks
2076+ ticks = ticks({ min: axis.min, max: axis.max });
2077+
2078+ // clean up the user-supplied ticks, copy them over
2079+ var i, v;
2080+ for (i = 0; i < ticks.length; ++i) {
2081+ var label = null;
2082+ var t = ticks[i];
2083+ if (typeof t == "object") {
2084+ v = t[0];
2085+ if (t.length > 1)
2086+ label = t[1];
2087+ }
2088+ else
2089+ v = t;
2090+ if (label == null)
2091+ label = axis.tickFormatter(v, axis);
2092+ axis.ticks[i] = { v: v, label: label };
2093+ }
2094+ }
2095+
2096+ if (axisOptions.autoscaleMargin != null && axis.ticks.length > 0) {
2097+ // snap to ticks
2098+ if (axisOptions.min == null)
2099+ axis.min = Math.min(axis.min, axis.ticks[0].v);
2100+ if (axisOptions.max == null && axis.ticks.length > 1)
2101+ axis.max = Math.max(axis.max, axis.ticks[axis.ticks.length - 1].v);
2102+ }
2103+ }
2104+
2105+ function draw() {
2106+ ctx.clearRect(0, 0, canvasWidth, canvasHeight);
2107+
2108+ var grid = options.grid;
2109+
2110+ if (grid.show && !grid.aboveData)
2111+ drawGrid();
2112+
2113+ for (var i = 0; i < series.length; ++i)
2114+ drawSeries(series[i]);
2115+
2116+ executeHooks(hooks.draw, [ctx]);
2117+
2118+ if (grid.show && grid.aboveData)
2119+ drawGrid();
2120+ }
2121+
2122+ function extractRange(ranges, coord) {
2123+ var firstAxis = coord + "axis",
2124+ secondaryAxis = coord + "2axis",
2125+ axis, from, to, reverse;
2126+
2127+ if (ranges[firstAxis]) {
2128+ axis = axes[firstAxis];
2129+ from = ranges[firstAxis].from;
2130+ to = ranges[firstAxis].to;
2131+ }
2132+ else if (ranges[secondaryAxis]) {
2133+ axis = axes[secondaryAxis];
2134+ from = ranges[secondaryAxis].from;
2135+ to = ranges[secondaryAxis].to;
2136+ }
2137+ else {
2138+ // backwards-compat stuff - to be removed in future
2139+ axis = axes[firstAxis];
2140+ from = ranges[coord + "1"];
2141+ to = ranges[coord + "2"];
2142+ }
2143+
2144+ // auto-reverse as an added bonus
2145+ if (from != null && to != null && from > to)
2146+ return { from: to, to: from, axis: axis };
2147+
2148+ return { from: from, to: to, axis: axis };
2149+ }
2150+
2151+ function drawGrid() {
2152+ var i;
2153+
2154+ ctx.save();
2155+ ctx.translate(plotOffset.left, plotOffset.top);
2156+
2157+ // draw background, if any
2158+ if (options.grid.backgroundColor) {
2159+ ctx.fillStyle = getColorOrGradient(options.grid.backgroundColor, plotHeight, 0, "rgba(255, 255, 255, 0)");
2160+ ctx.fillRect(0, 0, plotWidth, plotHeight);
2161+ }
2162+
2163+ // draw markings
2164+ var markings = options.grid.markings;
2165+ if (markings) {
2166+ if ($.isFunction(markings))
2167+ // xmin etc. are backwards-compatible, to be removed in future
2168+ markings = markings({ xmin: axes.xaxis.min, xmax: axes.xaxis.max, ymin: axes.yaxis.min, ymax: axes.yaxis.max, xaxis: axes.xaxis, yaxis: axes.yaxis, x2axis: axes.x2axis, y2axis: axes.y2axis });
2169+
2170+ for (i = 0; i < markings.length; ++i) {
2171+ var m = markings[i],
2172+ xrange = extractRange(m, "x"),
2173+ yrange = extractRange(m, "y");
2174+
2175+ // fill in missing
2176+ if (xrange.from == null)
2177+ xrange.from = xrange.axis.min;
2178+ if (xrange.to == null)
2179+ xrange.to = xrange.axis.max;
2180+ if (yrange.from == null)
2181+ yrange.from = yrange.axis.min;
2182+ if (yrange.to == null)
2183+ yrange.to = yrange.axis.max;
2184+
2185+ // clip
2186+ if (xrange.to < xrange.axis.min || xrange.from > xrange.axis.max ||
2187+ yrange.to < yrange.axis.min || yrange.from > yrange.axis.max)
2188+ continue;
2189+
2190+ xrange.from = Math.max(xrange.from, xrange.axis.min);
2191+ xrange.to = Math.min(xrange.to, xrange.axis.max);
2192+ yrange.from = Math.max(yrange.from, yrange.axis.min);
2193+ yrange.to = Math.min(yrange.to, yrange.axis.max);
2194+
2195+ if (xrange.from == xrange.to && yrange.from == yrange.to)
2196+ continue;
2197+
2198+ // then draw
2199+ xrange.from = xrange.axis.p2c(xrange.from);
2200+ xrange.to = xrange.axis.p2c(xrange.to);
2201+ yrange.from = yrange.axis.p2c(yrange.from);
2202+ yrange.to = yrange.axis.p2c(yrange.to);
2203+
2204+ if (xrange.from == xrange.to || yrange.from == yrange.to) {
2205+ // draw line
2206+ ctx.beginPath();
2207+ ctx.strokeStyle = m.color || options.grid.markingsColor;
2208+ ctx.lineWidth = m.lineWidth || options.grid.markingsLineWidth;
2209+ //ctx.moveTo(Math.floor(xrange.from), yrange.from);
2210+ //ctx.lineTo(Math.floor(xrange.to), yrange.to);
2211+ ctx.moveTo(xrange.from, yrange.from);
2212+ ctx.lineTo(xrange.to, yrange.to);
2213+ ctx.stroke();
2214+ }
2215+ else {
2216+ // fill area
2217+ ctx.fillStyle = m.color || options.grid.markingsColor;
2218+ ctx.fillRect(xrange.from, yrange.to,
2219+ xrange.to - xrange.from,
2220+ yrange.from - yrange.to);
2221+ }
2222+ }
2223+ }
2224+
2225+ // draw the inner grid
2226+ ctx.lineWidth = 1;
2227+ ctx.strokeStyle = options.grid.tickColor;
2228+ ctx.beginPath();
2229+ var v, axis = axes.xaxis;
2230+ for (i = 0; i < axis.ticks.length; ++i) {
2231+ v = axis.ticks[i].v;
2232+ if (v <= axis.min || v >= axes.xaxis.max)
2233+ continue; // skip those lying on the axes
2234+
2235+ ctx.moveTo(Math.floor(axis.p2c(v)) + ctx.lineWidth/2, 0);
2236+ ctx.lineTo(Math.floor(axis.p2c(v)) + ctx.lineWidth/2, plotHeight);
2237+ }
2238+
2239+ axis = axes.yaxis;
2240+ for (i = 0; i < axis.ticks.length; ++i) {
2241+ v = axis.ticks[i].v;
2242+ if (v <= axis.min || v >= axis.max)
2243+ continue;
2244+
2245+ ctx.moveTo(0, Math.floor(axis.p2c(v)) + ctx.lineWidth/2);
2246+ ctx.lineTo(plotWidth, Math.floor(axis.p2c(v)) + ctx.lineWidth/2);
2247+ }
2248+
2249+ axis = axes.x2axis;
2250+ for (i = 0; i < axis.ticks.length; ++i) {
2251+ v = axis.ticks[i].v;
2252+ if (v <= axis.min || v >= axis.max)
2253+ continue;
2254+
2255+ ctx.moveTo(Math.floor(axis.p2c(v)) + ctx.lineWidth/2, -5);
2256+ ctx.lineTo(Math.floor(axis.p2c(v)) + ctx.lineWidth/2, 5);
2257+ }
2258+
2259+ axis = axes.y2axis;
2260+ for (i = 0; i < axis.ticks.length; ++i) {
2261+ v = axis.ticks[i].v;
2262+ if (v <= axis.min || v >= axis.max)
2263+ continue;
2264+
2265+ ctx.moveTo(plotWidth-5, Math.floor(axis.p2c(v)) + ctx.lineWidth/2);
2266+ ctx.lineTo(plotWidth+5, Math.floor(axis.p2c(v)) + ctx.lineWidth/2);
2267+ }
2268+
2269+ ctx.stroke();
2270+
2271+ if (options.grid.borderWidth) {
2272+ // draw border
2273+ var bw = options.grid.borderWidth;
2274+ ctx.lineWidth = bw;
2275+ ctx.strokeStyle = options.grid.borderColor;
2276+ ctx.strokeRect(-bw/2, -bw/2, plotWidth + bw, plotHeight + bw);
2277+ }
2278+
2279+ ctx.restore();
2280+ }
2281+
2282+ function insertLabels() {
2283+ placeholder.find(".tickLabels").remove();
2284+
2285+ var html = ['<div class="tickLabels" style="font-size:smaller;color:' + options.grid.color + '">'];
2286+
2287+ function addLabels(axis, labelGenerator) {
2288+ for (var i = 0; i < axis.ticks.length; ++i) {
2289+ var tick = axis.ticks[i];
2290+ if (!tick.label || tick.v < axis.min || tick.v > axis.max)
2291+ continue;
2292+ html.push(labelGenerator(tick, axis));
2293+ }
2294+ }
2295+
2296+ var margin = options.grid.labelMargin + options.grid.borderWidth;
2297+
2298+ addLabels(axes.xaxis, function (tick, axis) {
2299+ return '<div style="position:absolute;top:' + (plotOffset.top + plotHeight + margin) + 'px;left:' + Math.round(plotOffset.left + axis.p2c(tick.v) - axis.labelWidth/2) + 'px;width:' + axis.labelWidth + 'px;text-align:center" class="tickLabel">' + tick.label + "</div>";
2300+ });
2301+
2302+
2303+ addLabels(axes.yaxis, function (tick, axis) {
2304+ return '<div style="position:absolute;top:' + Math.round(plotOffset.top + axis.p2c(tick.v) - axis.labelHeight/2) + 'px;right:' + (plotOffset.right + plotWidth + margin) + 'px;width:' + axis.labelWidth + 'px;text-align:right" class="tickLabel">' + tick.label + "</div>";
2305+ });
2306+
2307+ addLabels(axes.x2axis, function (tick, axis) {
2308+ return '<div style="position:absolute;bottom:' + (plotOffset.bottom + plotHeight + margin) + 'px;left:' + Math.round(plotOffset.left + axis.p2c(tick.v) - axis.labelWidth/2) + 'px;width:' + axis.labelWidth + 'px;text-align:center" class="tickLabel">' + tick.label + "</div>";
2309+ });
2310+
2311+ addLabels(axes.y2axis, function (tick, axis) {
2312+ return '<div style="position:absolute;top:' + Math.round(plotOffset.top + axis.p2c(tick.v) - axis.labelHeight/2) + 'px;left:' + (plotOffset.left + plotWidth + margin) +'px;width:' + axis.labelWidth + 'px;text-align:left" class="tickLabel">' + tick.label + "</div>";
2313+ });
2314+
2315+ html.push('</div>');
2316+
2317+ placeholder.append(html.join(""));
2318+ }
2319+
2320+ function drawSeries(series) {
2321+ if (series.lines.show)
2322+ drawSeriesLines(series);
2323+ if (series.bars.show)
2324+ drawSeriesBars(series);
2325+ if (series.points.show)
2326+ drawSeriesPoints(series);
2327+ }
2328+
2329+ function drawSeriesLines(series) {
2330+ function plotLine(datapoints, xoffset, yoffset, axisx, axisy) {
2331+ var points = datapoints.points,
2332+ ps = datapoints.pointsize,
2333+ prevx = null, prevy = null;
2334+
2335+ ctx.beginPath();
2336+ for (var i = ps; i < points.length; i += ps) {
2337+ var x1 = points[i - ps], y1 = points[i - ps + 1],
2338+ x2 = points[i], y2 = points[i + 1];
2339+
2340+ if (x1 == null || x2 == null)
2341+ continue;
2342+
2343+ // clip with ymin
2344+ if (y1 <= y2 && y1 < axisy.min) {
2345+ if (y2 < axisy.min)
2346+ continue; // line segment is outside
2347+ // compute new intersection point
2348+ x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
2349+ y1 = axisy.min;
2350+ }
2351+ else if (y2 <= y1 && y2 < axisy.min) {
2352+ if (y1 < axisy.min)
2353+ continue;
2354+ x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
2355+ y2 = axisy.min;
2356+ }
2357+
2358+ // clip with ymax
2359+ if (y1 >= y2 && y1 > axisy.max) {
2360+ if (y2 > axisy.max)
2361+ continue;
2362+ x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
2363+ y1 = axisy.max;
2364+ }
2365+ else if (y2 >= y1 && y2 > axisy.max) {
2366+ if (y1 > axisy.max)
2367+ continue;
2368+ x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
2369+ y2 = axisy.max;
2370+ }
2371+
2372+ // clip with xmin
2373+ if (x1 <= x2 && x1 < axisx.min) {
2374+ if (x2 < axisx.min)
2375+ continue;
2376+ y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
2377+ x1 = axisx.min;
2378+ }
2379+ else if (x2 <= x1 && x2 < axisx.min) {
2380+ if (x1 < axisx.min)
2381+ continue;
2382+ y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
2383+ x2 = axisx.min;
2384+ }
2385+
2386+ // clip with xmax
2387+ if (x1 >= x2 && x1 > axisx.max) {
2388+ if (x2 > axisx.max)
2389+ continue;
2390+ y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
2391+ x1 = axisx.max;
2392+ }
2393+ else if (x2 >= x1 && x2 > axisx.max) {
2394+ if (x1 > axisx.max)
2395+ continue;
2396+ y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
2397+ x2 = axisx.max;
2398+ }
2399+
2400+ if (x1 != prevx || y1 != prevy)
2401+ ctx.moveTo(axisx.p2c(x1) + xoffset, axisy.p2c(y1) + yoffset);
2402+
2403+ prevx = x2;
2404+ prevy = y2;
2405+ ctx.lineTo(axisx.p2c(x2) + xoffset, axisy.p2c(y2) + yoffset);
2406+ }
2407+ ctx.stroke();
2408+ }
2409+
2410+ function plotLineArea(datapoints, axisx, axisy) {
2411+ var points = datapoints.points,
2412+ ps = datapoints.pointsize,
2413+ bottom = Math.min(Math.max(0, axisy.min), axisy.max),
2414+ top, lastX = 0, areaOpen = false;
2415+
2416+ for (var i = ps; i < points.length; i += ps) {
2417+ var x1 = points[i - ps], y1 = points[i - ps + 1],
2418+ x2 = points[i], y2 = points[i + 1];
2419+
2420+ if (areaOpen && x1 != null && x2 == null) {
2421+ // close area
2422+ ctx.lineTo(axisx.p2c(lastX), axisy.p2c(bottom));
2423+ ctx.fill();
2424+ areaOpen = false;
2425+ continue;
2426+ }
2427+
2428+ if (x1 == null || x2 == null)
2429+ continue;
2430+
2431+ // clip x values
2432+
2433+ // clip with xmin
2434+ if (x1 <= x2 && x1 < axisx.min) {
2435+ if (x2 < axisx.min)
2436+ continue;
2437+ y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
2438+ x1 = axisx.min;
2439+ }
2440+ else if (x2 <= x1 && x2 < axisx.min) {
2441+ if (x1 < axisx.min)
2442+ continue;
2443+ y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
2444+ x2 = axisx.min;
2445+ }
2446+
2447+ // clip with xmax
2448+ if (x1 >= x2 && x1 > axisx.max) {
2449+ if (x2 > axisx.max)
2450+ continue;
2451+ y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
2452+ x1 = axisx.max;
2453+ }
2454+ else if (x2 >= x1 && x2 > axisx.max) {
2455+ if (x1 > axisx.max)
2456+ continue;
2457+ y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
2458+ x2 = axisx.max;
2459+ }
2460+
2461+ if (!areaOpen) {
2462+ // open area
2463+ ctx.beginPath();
2464+ ctx.moveTo(axisx.p2c(x1), axisy.p2c(bottom));
2465+ areaOpen = true;
2466+ }
2467+
2468+ // now first check the case where both is outside
2469+ if (y1 >= axisy.max && y2 >= axisy.max) {
2470+ ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.max));
2471+ ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.max));
2472+ lastX = x2;
2473+ continue;
2474+ }
2475+ else if (y1 <= axisy.min && y2 <= axisy.min) {
2476+ ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.min));
2477+ ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.min));
2478+ lastX = x2;
2479+ continue;
2480+ }
2481+
2482+ // else it's a bit more complicated, there might
2483+ // be two rectangles and two triangles we need to fill
2484+ // in; to find these keep track of the current x values
2485+ var x1old = x1, x2old = x2;
2486+
2487+ // and clip the y values, without shortcutting
2488+
2489+ // clip with ymin
2490+ if (y1 <= y2 && y1 < axisy.min && y2 >= axisy.min) {
2491+ x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
2492+ y1 = axisy.min;
2493+ }
2494+ else if (y2 <= y1 && y2 < axisy.min && y1 >= axisy.min) {
2495+ x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
2496+ y2 = axisy.min;
2497+ }
2498+
2499+ // clip with ymax
2500+ if (y1 >= y2 && y1 > axisy.max && y2 <= axisy.max) {
2501+ x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
2502+ y1 = axisy.max;
2503+ }
2504+ else if (y2 >= y1 && y2 > axisy.max && y1 <= axisy.max) {
2505+ x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
2506+ y2 = axisy.max;
2507+ }
2508+
2509+
2510+ // if the x value was changed we got a rectangle
2511+ // to fill
2512+ if (x1 != x1old) {
2513+ if (y1 <= axisy.min)
2514+ top = axisy.min;
2515+ else
2516+ top = axisy.max;
2517+
2518+ ctx.lineTo(axisx.p2c(x1old), axisy.p2c(top));
2519+ ctx.lineTo(axisx.p2c(x1), axisy.p2c(top));
2520+ }
2521+
2522+ // fill the triangles
2523+ ctx.lineTo(axisx.p2c(x1), axisy.p2c(y1));
2524+ ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2));
2525+
2526+ // fill the other rectangle if it's there
2527+ if (x2 != x2old) {
2528+ if (y2 <= axisy.min)
2529+ top = axisy.min;
2530+ else
2531+ top = axisy.max;
2532+
2533+ ctx.lineTo(axisx.p2c(x2), axisy.p2c(top));
2534+ ctx.lineTo(axisx.p2c(x2old), axisy.p2c(top));
2535+ }
2536+
2537+ lastX = Math.max(x2, x2old);
2538+ }
2539+
2540+ if (areaOpen) {
2541+ ctx.lineTo(axisx.p2c(lastX), axisy.p2c(bottom));
2542+ ctx.fill();
2543+ }
2544+ }
2545+
2546+ ctx.save();
2547+ ctx.translate(plotOffset.left, plotOffset.top);
2548+ ctx.lineJoin = "round";
2549+
2550+ var lw = series.lines.lineWidth,
2551+ sw = series.shadowSize;
2552+ // FIXME: consider another form of shadow when filling is turned on
2553+ if (lw > 0 && sw > 0) {
2554+ // draw shadow as a thick and thin line with transparency
2555+ ctx.lineWidth = sw;
2556+ ctx.strokeStyle = "rgba(0,0,0,0.1)";
2557+ // position shadow at angle from the mid of line
2558+ var angle = Math.PI/18;
2559+ plotLine(series.datapoints, Math.sin(angle) * (lw/2 + sw/2), Math.cos(angle) * (lw/2 + sw/2), series.xaxis, series.yaxis);
2560+ ctx.lineWidth = sw/2;
2561+ plotLine(series.datapoints, Math.sin(angle) * (lw/2 + sw/4), Math.cos(angle) * (lw/2 + sw/4), series.xaxis, series.yaxis);
2562+ }
2563+
2564+ ctx.lineWidth = lw;
2565+ ctx.strokeStyle = series.color;
2566+ var fillStyle = getFillStyle(series.lines, series.color, 0, plotHeight);
2567+ if (fillStyle) {
2568+ ctx.fillStyle = fillStyle;
2569+ plotLineArea(series.datapoints, series.xaxis, series.yaxis);
2570+ }
2571+
2572+ if (lw > 0)
2573+ plotLine(series.datapoints, 0, 0, series.xaxis, series.yaxis);
2574+ ctx.restore();
2575+ }
2576+
2577+ function drawSeriesPoints(series) {
2578+ function plotPoints(datapoints, radius, fillStyle, offset, circumference, axisx, axisy) {
2579+ var points = datapoints.points, ps = datapoints.pointsize;
2580+
2581+ for (var i = 0; i < points.length; i += ps) {
2582+ var x = points[i], y = points[i + 1];
2583+ if (x == null || x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max)
2584+ continue;
2585+
2586+ ctx.beginPath();
2587+ ctx.arc(axisx.p2c(x), axisy.p2c(y) + offset, radius, 0, circumference, false);
2588+ if (fillStyle) {
2589+ ctx.fillStyle = fillStyle;
2590+ ctx.fill();
2591+ }
2592+ ctx.stroke();
2593+ }
2594+ }
2595+
2596+ ctx.save();
2597+ ctx.translate(plotOffset.left, plotOffset.top);
2598+
2599+ var lw = series.lines.lineWidth,
2600+ sw = series.shadowSize,
2601+ radius = series.points.radius;
2602+ if (lw > 0 && sw > 0) {
2603+ // draw shadow in two steps
2604+ var w = sw / 2;
2605+ ctx.lineWidth = w;
2606+ ctx.strokeStyle = "rgba(0,0,0,0.1)";
2607+ plotPoints(series.datapoints, radius, null, w + w/2, Math.PI,
2608+ series.xaxis, series.yaxis);
2609+
2610+ ctx.strokeStyle = "rgba(0,0,0,0.2)";
2611+ plotPoints(series.datapoints, radius, null, w/2, Math.PI,
2612+ series.xaxis, series.yaxis);
2613+ }
2614+
2615+ ctx.lineWidth = lw;
2616+ ctx.strokeStyle = series.color;
2617+ plotPoints(series.datapoints, radius,
2618+ getFillStyle(series.points, series.color), 0, 2 * Math.PI,
2619+ series.xaxis, series.yaxis);
2620+ ctx.restore();
2621+ }
2622+
2623+ function drawBar(x, y, b, barLeft, barRight, offset, fillStyleCallback, axisx, axisy, c, horizontal) {
2624+ var left, right, bottom, top,
2625+ drawLeft, drawRight, drawTop, drawBottom,
2626+ tmp;
2627+
2628+ if (horizontal) {
2629+ drawBottom = drawRight = drawTop = true;
2630+ drawLeft = false;
2631+ left = b;
2632+ right = x;
2633+ top = y + barLeft;
2634+ bottom = y + barRight;
2635+
2636+ // account for negative bars
2637+ if (right < left) {
2638+ tmp = right;
2639+ right = left;
2640+ left = tmp;
2641+ drawLeft = true;
2642+ drawRight = false;
2643+ }
2644+ }
2645+ else {
2646+ drawLeft = drawRight = drawTop = true;
2647+ drawBottom = false;
2648+ left = x + barLeft;
2649+ right = x + barRight;
2650+ bottom = b;
2651+ top = y;
2652+
2653+ // account for negative bars
2654+ if (top < bottom) {
2655+ tmp = top;
2656+ top = bottom;
2657+ bottom = tmp;
2658+ drawBottom = true;
2659+ drawTop = false;
2660+ }
2661+ }
2662+
2663+ // clip
2664+ if (right < axisx.min || left > axisx.max ||
2665+ top < axisy.min || bottom > axisy.max)
2666+ return;
2667+
2668+ if (left < axisx.min) {
2669+ left = axisx.min;
2670+ drawLeft = false;
2671+ }
2672+
2673+ if (right > axisx.max) {
2674+ right = axisx.max;
2675+ drawRight = false;
2676+ }
2677+
2678+ if (bottom < axisy.min) {
2679+ bottom = axisy.min;
2680+ drawBottom = false;
2681+ }
2682+
2683+ if (top > axisy.max) {
2684+ top = axisy.max;
2685+ drawTop = false;
2686+ }
2687+
2688+ left = axisx.p2c(left);
2689+ bottom = axisy.p2c(bottom);
2690+ right = axisx.p2c(right);
2691+ top = axisy.p2c(top);
2692+
2693+ // fill the bar
2694+ if (fillStyleCallback) {
2695+ c.beginPath();
2696+ c.moveTo(left, bottom);
2697+ c.lineTo(left, top);
2698+ c.lineTo(right, top);
2699+ c.lineTo(right, bottom);
2700+ c.fillStyle = fillStyleCallback(bottom, top);
2701+ c.fill();
2702+ }
2703+
2704+ // draw outline
2705+ if (drawLeft || drawRight || drawTop || drawBottom) {
2706+ c.beginPath();
2707+
2708+ // FIXME: inline moveTo is buggy with excanvas
2709+ c.moveTo(left, bottom + offset);
2710+ if (drawLeft)
2711+ c.lineTo(left, top + offset);
2712+ else
2713+ c.moveTo(left, top + offset);
2714+ if (drawTop)
2715+ c.lineTo(right, top + offset);
2716+ else
2717+ c.moveTo(right, top + offset);
2718+ if (drawRight)
2719+ c.lineTo(right, bottom + offset);
2720+ else
2721+ c.moveTo(right, bottom + offset);
2722+ if (drawBottom)
2723+ c.lineTo(left, bottom + offset);
2724+ else
2725+ c.moveTo(left, bottom + offset);
2726+ c.stroke();
2727+ }
2728+ }
2729+
2730+ function drawSeriesBars(series) {
2731+ function plotBars(datapoints, barLeft, barRight, offset, fillStyleCallback, axisx, axisy) {
2732+ var points = datapoints.points, ps = datapoints.pointsize;
2733+
2734+ for (var i = 0; i < points.length; i += ps) {
2735+ if (points[i] == null)
2736+ continue;
2737+ drawBar(points[i], points[i + 1], points[i + 2], barLeft, barRight, offset, fillStyleCallback, axisx, axisy, ctx, series.bars.horizontal);
2738+ }
2739+ }
2740+
2741+ ctx.save();
2742+ ctx.translate(plotOffset.left, plotOffset.top);
2743+
2744+ // FIXME: figure out a way to add shadows (for instance along the right edge)
2745+ ctx.lineWidth = series.bars.lineWidth;
2746+ ctx.strokeStyle = series.color;
2747+ var barLeft = series.bars.align == "left" ? 0 : -series.bars.barWidth/2;
2748+ var fillStyleCallback = series.bars.fill ? function (bottom, top) { return getFillStyle(series.bars, series.color, bottom, top); } : null;
2749+ plotBars(series.datapoints, barLeft, barLeft + series.bars.barWidth, 0, fillStyleCallback, series.xaxis, series.yaxis);
2750+ ctx.restore();
2751+ }
2752+
2753+ function getFillStyle(filloptions, seriesColor, bottom, top) {
2754+ var fill = filloptions.fill;
2755+ if (!fill)
2756+ return null;
2757+
2758+ if (filloptions.fillColor)
2759+ return getColorOrGradient(filloptions.fillColor, bottom, top, seriesColor);
2760+
2761+ var c = $.color.parse(seriesColor);
2762+ c.a = typeof fill == "number" ? fill : 0.4;
2763+ c.normalize();
2764+ return c.toString();
2765+ }
2766+
2767+ function insertLegend() {
2768+ placeholder.find(".legend").remove();
2769+
2770+ if (!options.legend.show)
2771+ return;
2772+
2773+ var fragments = [], rowStarted = false,
2774+ lf = options.legend.labelFormatter, s, label;
2775+ for (i = 0; i < series.length; ++i) {
2776+ s = series[i];
2777+ label = s.label;
2778+ if (!label)
2779+ continue;
2780+
2781+ if (i % options.legend.noColumns == 0) {
2782+ if (rowStarted)
2783+ fragments.push('</tr>');
2784+ fragments.push('<tr>');
2785+ rowStarted = true;
2786+ }
2787+
2788+ if (lf)
2789+ label = lf(label, s);
2790+
2791+ fragments.push(
2792+ '<td class="legendColorBox"><div style="border:1px solid ' + options.legend.labelBoxBorderColor + ';padding:1px"><div style="width:4px;height:0;border:5px solid ' + s.color + ';overflow:hidden"></div></div></td>' +
2793+ '<td class="legendLabel">' + label + '</td>');
2794+ }
2795+ if (rowStarted)
2796+ fragments.push('</tr>');
2797+
2798+ if (fragments.length == 0)
2799+ return;
2800+
2801+ var table = '<table style="font-size:smaller;color:' + options.grid.color + '">' + fragments.join("") + '</table>';
2802+ if (options.legend.container != null)
2803+ $(options.legend.container).html(table);
2804+ else {
2805+ var pos = "",
2806+ p = options.legend.position,
2807+ m = options.legend.margin;
2808+ if (m[0] == null)
2809+ m = [m, m];
2810+ if (p.charAt(0) == "n")
2811+ pos += 'top:' + (m[1] + plotOffset.top) + 'px;';
2812+ else if (p.charAt(0) == "s")
2813+ pos += 'bottom:' + (m[1] + plotOffset.bottom) + 'px;';
2814+ if (p.charAt(1) == "e")
2815+ pos += 'right:' + (m[0] + plotOffset.right) + 'px;';
2816+ else if (p.charAt(1) == "w")
2817+ pos += 'left:' + (m[0] + plotOffset.left) + 'px;';
2818+ var legend = $('<div class="legend">' + table.replace('style="', 'style="position:absolute;' + pos +';') + '</div>').appendTo(placeholder);
2819+ if (options.legend.backgroundOpacity != 0.0) {
2820+ // put in the transparent background
2821+ // separately to avoid blended labels and
2822+ // label boxes
2823+ var c = options.legend.backgroundColor;
2824+ if (c == null) {
2825+ c = options.grid.backgroundColor;
2826+ if (c && typeof c == "string")
2827+ c = $.color.parse(c);
2828+ else
2829+ c = $.color.extract(legend, 'background-color');
2830+ c.a = 1;
2831+ c = c.toString();
2832+ }
2833+ var div = legend.children();
2834+ $('<div style="position:absolute;width:' + div.width() + 'px;height:' + div.height() + 'px;' + pos +'background-color:' + c + ';"> </div>').prependTo(legend).css('opacity', options.legend.backgroundOpacity);
2835+ }
2836+ }
2837+ }
2838+
2839+
2840+ // interactive features
2841+
2842+ var highlights = [],
2843+ redrawTimeout = null;
2844+
2845+ // returns the data item the mouse is over, or null if none is found
2846+ function findNearbyItem(mouseX, mouseY, seriesFilter) {
2847+ var maxDistance = options.grid.mouseActiveRadius,
2848+ smallestDistance = maxDistance * maxDistance + 1,
2849+ item = null, foundPoint = false, i, j;
2850+
2851+ for (i = 0; i < series.length; ++i) {
2852+ if (!seriesFilter(series[i]))
2853+ continue;
2854+
2855+ var s = series[i],
2856+ axisx = s.xaxis,
2857+ axisy = s.yaxis,
2858+ points = s.datapoints.points,
2859+ ps = s.datapoints.pointsize,
2860+ mx = axisx.c2p(mouseX), // precompute some stuff to make the loop faster
2861+ my = axisy.c2p(mouseY),
2862+ maxx = maxDistance / axisx.scale,
2863+ maxy = maxDistance / axisy.scale;
2864+
2865+ if (s.lines.show || s.points.show) {
2866+ for (j = 0; j < points.length; j += ps) {
2867+ var x = points[j], y = points[j + 1];
2868+ if (x == null)
2869+ continue;
2870+
2871+ // For points and lines, the cursor must be within a
2872+ // certain distance to the data point
2873+ if (x - mx > maxx || x - mx < -maxx ||
2874+ y - my > maxy || y - my < -maxy)
2875+ continue;
2876+
2877+ // We have to calculate distances in pixels, not in
2878+ // data units, because the scales of the axes may be different
2879+ var dx = Math.abs(axisx.p2c(x) - mouseX),
2880+ dy = Math.abs(axisy.p2c(y) - mouseY),
2881+ dist = dx * dx + dy * dy; // we save the sqrt
2882+
2883+ // use <= to ensure last point takes precedence
2884+ // (last generally means on top of)
2885+ if (dist <= smallestDistance) {
2886+ smallestDistance = dist;
2887+ item = [i, j / ps];
2888+ }
2889+ }
2890+ }
2891+
2892+ if (s.bars.show && !item) { // no other point can be nearby
2893+ var barLeft = s.bars.align == "left" ? 0 : -s.bars.barWidth/2,
2894+ barRight = barLeft + s.bars.barWidth;
2895+
2896+ for (j = 0; j < points.length; j += ps) {
2897+ var x = points[j], y = points[j + 1], b = points[j + 2];
2898+ if (x == null)
2899+ continue;
2900+
2901+ // for a bar graph, the cursor must be inside the bar
2902+ if (series[i].bars.horizontal ?
2903+ (mx <= Math.max(b, x) && mx >= Math.min(b, x) &&
2904+ my >= y + barLeft && my <= y + barRight) :
2905+ (mx >= x + barLeft && mx <= x + barRight &&
2906+ my >= Math.min(b, y) && my <= Math.max(b, y)))
2907+ item = [i, j / ps];
2908+ }
2909+ }
2910+ }
2911+
2912+ if (item) {
2913+ i = item[0];
2914+ j = item[1];
2915+ ps = series[i].datapoints.pointsize;
2916+
2917+ return { datapoint: series[i].datapoints.points.slice(j * ps, (j + 1) * ps),
2918+ dataIndex: j,
2919+ series: series[i],
2920+ seriesIndex: i };
2921+ }
2922+
2923+ return null;
2924+ }
2925+
2926+ function onMouseMove(e) {
2927+ if (options.grid.hoverable)
2928+ triggerClickHoverEvent("plothover", e,
2929+ function (s) { return s["hoverable"] != false; });
2930+ }
2931+
2932+ function onClick(e) {
2933+ triggerClickHoverEvent("plotclick", e,
2934+ function (s) { return s["clickable"] != false; });
2935+ }
2936+
2937+ // trigger click or hover event (they send the same parameters
2938+ // so we share their code)
2939+ function triggerClickHoverEvent(eventname, event, seriesFilter) {
2940+ var offset = eventHolder.offset(),
2941+ pos = { pageX: event.pageX, pageY: event.pageY },
2942+ canvasX = event.pageX - offset.left - plotOffset.left,
2943+ canvasY = event.pageY - offset.top - plotOffset.top;
2944+
2945+ if (axes.xaxis.used)
2946+ pos.x = axes.xaxis.c2p(canvasX);
2947+ if (axes.yaxis.used)
2948+ pos.y = axes.yaxis.c2p(canvasY);
2949+ if (axes.x2axis.used)
2950+ pos.x2 = axes.x2axis.c2p(canvasX);
2951+ if (axes.y2axis.used)
2952+ pos.y2 = axes.y2axis.c2p(canvasY);
2953+
2954+ var item = findNearbyItem(canvasX, canvasY, seriesFilter);
2955+
2956+ if (item) {
2957+ // fill in mouse pos for any listeners out there
2958+ item.pageX = parseInt(item.series.xaxis.p2c(item.datapoint[0]) + offset.left + plotOffset.left);
2959+ item.pageY = parseInt(item.series.yaxis.p2c(item.datapoint[1]) + offset.top + plotOffset.top);
2960+ }
2961+
2962+ if (options.grid.autoHighlight) {
2963+ // clear auto-highlights
2964+ for (var i = 0; i < highlights.length; ++i) {
2965+ var h = highlights[i];
2966+ if (h.auto == eventname &&
2967+ !(item && h.series == item.series && h.point == item.datapoint))
2968+ unhighlight(h.series, h.point);
2969+ }
2970+
2971+ if (item)
2972+ highlight(item.series, item.datapoint, eventname);
2973+ }
2974+
2975+ placeholder.trigger(eventname, [ pos, item ]);
2976+ }
2977+
2978+ function triggerRedrawOverlay() {
2979+ if (!redrawTimeout)
2980+ redrawTimeout = setTimeout(drawOverlay, 30);
2981+ }
2982+
2983+ function drawOverlay() {
2984+ redrawTimeout = null;
2985+
2986+ // draw highlights
2987+ octx.save();
2988+ octx.clearRect(0, 0, canvasWidth, canvasHeight);
2989+ octx.translate(plotOffset.left, plotOffset.top);
2990+
2991+ var i, hi;
2992+ for (i = 0; i < highlights.length; ++i) {
2993+ hi = highlights[i];
2994+
2995+ if (hi.series.bars.show)
2996+ drawBarHighlight(hi.series, hi.point);
2997+ else
2998+ drawPointHighlight(hi.series, hi.point);
2999+ }
3000+ octx.restore();
3001+
3002+ executeHooks(hooks.drawOverlay, [octx]);
3003+ }
3004+
3005+ function highlight(s, point, auto) {
3006+ if (typeof s == "number")
3007+ s = series[s];
3008+
3009+ if (typeof point == "number")
3010+ point = s.data[point];
3011+
3012+ var i = indexOfHighlight(s, point);
3013+ if (i == -1) {
3014+ highlights.push({ series: s, point: point, auto: auto });
3015+
3016+ triggerRedrawOverlay();
3017+ }
3018+ else if (!auto)
3019+ highlights[i].auto = false;
3020+ }
3021+
3022+ function unhighlight(s, point) {
3023+ if (s == null && point == null) {
3024+ highlights = [];
3025+ triggerRedrawOverlay();
3026+ }
3027+
3028+ if (typeof s == "number")
3029+ s = series[s];
3030+
3031+ if (typeof point == "number")
3032+ point = s.data[point];
3033+
3034+ var i = indexOfHighlight(s, point);
3035+ if (i != -1) {
3036+ highlights.splice(i, 1);
3037+
3038+ triggerRedrawOverlay();
3039+ }
3040+ }
3041+
3042+ function indexOfHighlight(s, p) {
3043+ for (var i = 0; i < highlights.length; ++i) {
3044+ var h = highlights[i];
3045+ if (h.series == s && h.point[0] == p[0]
3046+ && h.point[1] == p[1])
3047+ return i;
3048+ }
3049+ return -1;
3050+ }
3051+
3052+ function drawPointHighlight(series, point) {
3053+ var x = point[0], y = point[1],
3054+ axisx = series.xaxis, axisy = series.yaxis;
3055+
3056+ if (x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max)
3057+ return;
3058+
3059+ var pointRadius = series.points.radius + series.points.lineWidth / 2;
3060+ octx.lineWidth = pointRadius;
3061+ octx.strokeStyle = $.color.parse(series.color).scale('a', 0.5).toString();
3062+ var radius = 1.5 * pointRadius;
3063+ octx.beginPath();
3064+ octx.arc(axisx.p2c(x), axisy.p2c(y), radius, 0, 2 * Math.PI, false);
3065+ octx.stroke();
3066+ }
3067+
3068+ function drawBarHighlight(series, point) {
3069+ octx.lineWidth = series.bars.lineWidth;
3070+ octx.strokeStyle = $.color.parse(series.color).scale('a', 0.5).toString();
3071+ var fillStyle = $.color.parse(series.color).scale('a', 0.5).toString();
3072+ var barLeft = series.bars.align == "left" ? 0 : -series.bars.barWidth/2;
3073+ drawBar(point[0], point[1], point[2] || 0, barLeft, barLeft + series.bars.barWidth,
3074+ 0, function () { return fillStyle; }, series.xaxis, series.yaxis, octx, series.bars.horizontal);
3075+ }
3076+
3077+ function getColorOrGradient(spec, bottom, top, defaultColor) {
3078+ if (typeof spec == "string")
3079+ return spec;
3080+ else {
3081+ // assume this is a gradient spec; IE currently only
3082+ // supports a simple vertical gradient properly, so that's
3083+ // what we support too
3084+ var gradient = ctx.createLinearGradient(0, top, 0, bottom);
3085+
3086+ for (var i = 0, l = spec.colors.length; i < l; ++i) {
3087+ var c = spec.colors[i];
3088+ if (typeof c != "string") {
3089+ c = $.color.parse(defaultColor).scale('rgb', c.brightness);
3090+ c.a *= c.opacity;
3091+ c = c.toString();
3092+ }
3093+ gradient.addColorStop(i / (l - 1), c);
3094+ }
3095+
3096+ return gradient;
3097+ }
3098+ }
3099+ }
3100+
3101+ $.plot = function(placeholder, data, options) {
3102+ var plot = new Plot($(placeholder), data, options, $.plot.plugins);
3103+ /*var t0 = new Date();
3104+ var t1 = new Date();
3105+ var tstr = "time used (msecs): " + (t1.getTime() - t0.getTime())
3106+ if (window.console)
3107+ console.log(tstr);
3108+ else
3109+ alert(tstr);*/
3110+ return plot;
3111+ };
3112+
3113+ $.plot.plugins = [];
3114+
3115+ // returns a string with the date d formatted according to fmt
3116+ $.plot.formatDate = function(d, fmt, monthNames) {
3117+ var leftPad = function(n) {
3118+ n = "" + n;
3119+ return n.length == 1 ? "0" + n : n;
3120+ };
3121+
3122+ var r = [];
3123+ var escape = false;
3124+ var hours = d.getUTCHours();
3125+ var isAM = hours < 12;
3126+ if (monthNames == null)
3127+ monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
3128+
3129+ if (fmt.search(/%p|%P/) != -1) {
3130+ if (hours > 12) {
3131+ hours = hours - 12;
3132+ } else if (hours == 0) {
3133+ hours = 12;
3134+ }
3135+ }
3136+ for (var i = 0; i < fmt.length; ++i) {
3137+ var c = fmt.charAt(i);
3138+
3139+ if (escape) {
3140+ switch (c) {
3141+ case 'h': c = "" + hours; break;
3142+ case 'H': c = leftPad(hours); break;
3143+ case 'M': c = leftPad(d.getUTCMinutes()); break;
3144+ case 'S': c = leftPad(d.getUTCSeconds()); break;
3145+ case 'd': c = "" + d.getUTCDate(); break;
3146+ case 'm': c = "" + (d.getUTCMonth() + 1); break;
3147+ case 'y': c = "" + d.getUTCFullYear(); break;
3148+ case 'b': c = "" + monthNames[d.getUTCMonth()]; break;
3149+ case 'p': c = (isAM) ? ("" + "am") : ("" + "pm"); break;
3150+ case 'P': c = (isAM) ? ("" + "AM") : ("" + "PM"); break;
3151+ }
3152+ r.push(c);
3153+ escape = false;
3154+ }
3155+ else {
3156+ if (c == "%")
3157+ escape = true;
3158+ else
3159+ r.push(c);
3160+ }
3161+ }
3162+ return r.join("");
3163+ };
3164+
3165+ // round to nearby lower multiple of base
3166+ function floorInBase(n, base) {
3167+ return base * Math.floor(n / base);
3168+ }
3169+
3170+})(jQuery);
3171
3172=== added file 'webapp/content/js/jquery.flot.selection.js'
3173--- webapp/content/js/jquery.flot.selection.js 1970-01-01 00:00:00 +0000
3174+++ webapp/content/js/jquery.flot.selection.js 2010-11-01 18:48:50 +0000
3175@@ -0,0 +1,299 @@
3176+/*
3177+Flot plugin for selecting regions.
3178+
3179+The plugin defines the following options:
3180+
3181+ selection: {
3182+ mode: null or "x" or "y" or "xy",
3183+ color: color
3184+ }
3185+
3186+You enable selection support by setting the mode to one of "x", "y" or
3187+"xy". In "x" mode, the user will only be able to specify the x range,
3188+similarly for "y" mode. For "xy", the selection becomes a rectangle
3189+where both ranges can be specified. "color" is color of the selection.
3190+
3191+When selection support is enabled, a "plotselected" event will be emitted
3192+on the DOM element you passed into the plot function. The event
3193+handler gets one extra parameter with the ranges selected on the axes,
3194+like this:
3195+
3196+ placeholder.bind("plotselected", function(event, ranges) {
3197+ alert("You selected " + ranges.xaxis.from + " to " + ranges.xaxis.to)
3198+ // similar for yaxis, secondary axes are in x2axis
3199+ // and y2axis if present
3200+ });
3201+
3202+The "plotselected" event is only fired when the user has finished
3203+making the selection. A "plotselecting" event is fired during the
3204+process with the same parameters as the "plotselected" event, in case
3205+you want to know what's happening while it's happening,
3206+
3207+A "plotunselected" event with no arguments is emitted when the user
3208+clicks the mouse to remove the selection.
3209+
3210+The plugin allso adds the following methods to the plot object:
3211+
3212+- setSelection(ranges, preventEvent)
3213+
3214+ Set the selection rectangle. The passed in ranges is on the same
3215+ form as returned in the "plotselected" event. If the selection
3216+ mode is "x", you should put in either an xaxis (or x2axis) object,
3217+ if the mode is "y" you need to put in an yaxis (or y2axis) object
3218+ and both xaxis/x2axis and yaxis/y2axis if the selection mode is
3219+ "xy", like this:
3220+
3221+ setSelection({ xaxis: { from: 0, to: 10 }, yaxis: { from: 40, to: 60 } });
3222+
3223+ setSelection will trigger the "plotselected" event when called. If
3224+ you don't want that to happen, e.g. if you're inside a
3225+ "plotselected" handler, pass true as the second parameter.
3226+
3227+- clearSelection(preventEvent)
3228+
3229+ Clear the selection rectangle. Pass in true to avoid getting a
3230+ "plotunselected" event.
3231+
3232+- getSelection()
3233+
3234+ Returns the current selection in the same format as the
3235+ "plotselected" event. If there's currently no selection, the
3236+ function returns null.
3237+
3238+*/
3239+
3240+(function ($) {
3241+ function init(plot) {
3242+ var selection = {
3243+ first: { x: -1, y: -1}, second: { x: -1, y: -1},
3244+ show: false,
3245+ active: false
3246+ };
3247+
3248+ // FIXME: The drag handling implemented here should be
3249+ // abstracted out, there's some similar code from a library in
3250+ // the navigation plugin, this should be massaged a bit to fit
3251+ // the Flot cases here better and reused. Doing this would
3252+ // make this plugin much slimmer.
3253+ var savedhandlers = {};
3254+
3255+ function onMouseMove(e) {
3256+ if (selection.active) {
3257+ plot.getPlaceholder().trigger("plotselecting", [ getSelection() ]);
3258+
3259+ updateSelection(e);
3260+ }
3261+ }
3262+
3263+ function onMouseDown(e) {
3264+ if (e.which != 1) // only accept left-click
3265+ return;
3266+
3267+ // cancel out any text selections
3268+ document.body.focus();
3269+
3270+ // prevent text selection and drag in old-school browsers
3271+ if (document.onselectstart !== undefined && savedhandlers.onselectstart == null) {
3272+ savedhandlers.onselectstart = document.onselectstart;
3273+ document.onselectstart = function () { return false; };
3274+ }
3275+ if (document.ondrag !== undefined && savedhandlers.ondrag == null) {
3276+ savedhandlers.ondrag = document.ondrag;
3277+ document.ondrag = function () { return false; };
3278+ }
3279+
3280+ setSelectionPos(selection.first, e);
3281+
3282+ selection.active = true;
3283+
3284+ $(document).one("mouseup", onMouseUp);
3285+ }
3286+
3287+ function onMouseUp(e) {
3288+ // revert drag stuff for old-school browsers
3289+ if (document.onselectstart !== undefined)
3290+ document.onselectstart = savedhandlers.onselectstart;
3291+ if (document.ondrag !== undefined)
3292+ document.ondrag = savedhandlers.ondrag;
3293+
3294+ // no more draggy-dee-drag
3295+ selection.active = false;
3296+ updateSelection(e);
3297+
3298+ if (selectionIsSane())
3299+ triggerSelectedEvent();
3300+ else {
3301+ // this counts as a clear
3302+ plot.getPlaceholder().trigger("plotunselected", [ ]);
3303+ plot.getPlaceholder().trigger("plotselecting", [ null ]);
3304+ }
3305+
3306+ return false;
3307+ }
3308+
3309+ function getSelection() {
3310+ if (!selectionIsSane())
3311+ return null;
3312+
3313+ var x1 = Math.min(selection.first.x, selection.second.x),
3314+ x2 = Math.max(selection.first.x, selection.second.x),
3315+ y1 = Math.max(selection.first.y, selection.second.y),
3316+ y2 = Math.min(selection.first.y, selection.second.y);
3317+
3318+ var r = {};
3319+ var axes = plot.getAxes();
3320+ if (axes.xaxis.used)
3321+ r.xaxis = { from: axes.xaxis.c2p(x1), to: axes.xaxis.c2p(x2) };
3322+ if (axes.x2axis.used)
3323+ r.x2axis = { from: axes.x2axis.c2p(x1), to: axes.x2axis.c2p(x2) };
3324+ if (axes.yaxis.used)
3325+ r.yaxis = { from: axes.yaxis.c2p(y1), to: axes.yaxis.c2p(y2) };
3326+ if (axes.y2axis.used)
3327+ r.y2axis = { from: axes.y2axis.c2p(y1), to: axes.y2axis.c2p(y2) };
3328+ return r;
3329+ }
3330+
3331+ function triggerSelectedEvent() {
3332+ var r = getSelection();
3333+
3334+ plot.getPlaceholder().trigger("plotselected", [ r ]);
3335+
3336+ // backwards-compat stuff, to be removed in future
3337+ var axes = plot.getAxes();
3338+ if (axes.xaxis.used && axes.yaxis.used)
3339+ plot.getPlaceholder().trigger("selected", [ { x1: r.xaxis.from, y1: r.yaxis.from, x2: r.xaxis.to, y2: r.yaxis.to } ]);
3340+ }
3341+
3342+ function clamp(min, value, max) {
3343+ return value < min? min: (value > max? max: value);
3344+ }
3345+
3346+ function setSelectionPos(pos, e) {
3347+ var o = plot.getOptions();
3348+ var offset = plot.getPlaceholder().offset();
3349+ var plotOffset = plot.getPlotOffset();
3350+ pos.x = clamp(0, e.pageX - offset.left - plotOffset.left, plot.width());
3351+ pos.y = clamp(0, e.pageY - offset.top - plotOffset.top, plot.height());
3352+
3353+ if (o.selection.mode == "y")
3354+ pos.x = pos == selection.first? 0: plot.width();
3355+
3356+ if (o.selection.mode == "x")
3357+ pos.y = pos == selection.first? 0: plot.height();
3358+ }
3359+
3360+ function updateSelection(pos) {
3361+ if (pos.pageX == null)
3362+ return;
3363+
3364+ setSelectionPos(selection.second, pos);
3365+ if (selectionIsSane()) {
3366+ selection.show = true;
3367+ plot.triggerRedrawOverlay();
3368+ }
3369+ else
3370+ clearSelection(true);
3371+ }
3372+
3373+ function clearSelection(preventEvent) {
3374+ if (selection.show) {
3375+ selection.show = false;
3376+ plot.triggerRedrawOverlay();
3377+ if (!preventEvent)
3378+ plot.getPlaceholder().trigger("plotunselected", [ ]);
3379+ }
3380+ }
3381+
3382+ function setSelection(ranges, preventEvent) {
3383+ var axis, range, axes = plot.getAxes();
3384+ var o = plot.getOptions();
3385+
3386+ if (o.selection.mode == "y") {
3387+ selection.first.x = 0;
3388+ selection.second.x = plot.width();
3389+ }
3390+ else {
3391+ axis = ranges["xaxis"]? axes["xaxis"]: (ranges["x2axis"]? axes["x2axis"]: axes["xaxis"]);
3392+ range = ranges["xaxis"] || ranges["x2axis"] || { from:ranges["x1"], to:ranges["x2"] }
3393+ selection.first.x = axis.p2c(Math.min(range.from, range.to));
3394+ selection.second.x = axis.p2c(Math.max(range.from, range.to));
3395+ }
3396+
3397+ if (o.selection.mode == "x") {
3398+ selection.first.y = 0;
3399+ selection.second.y = plot.height();
3400+ }
3401+ else {
3402+ axis = ranges["yaxis"]? axes["yaxis"]: (ranges["y2axis"]? axes["y2axis"]: axes["yaxis"]);
3403+ range = ranges["yaxis"] || ranges["y2axis"] || { from:ranges["y1"], to:ranges["y2"] }
3404+ selection.first.y = axis.p2c(Math.min(range.from, range.to));
3405+ selection.second.y = axis.p2c(Math.max(range.from, range.to));
3406+ }
3407+
3408+ selection.show = true;
3409+ plot.triggerRedrawOverlay();
3410+ if (!preventEvent)
3411+ triggerSelectedEvent();
3412+ }
3413+
3414+ function selectionIsSane() {
3415+ var minSize = 5;
3416+ return Math.abs(selection.second.x - selection.first.x) >= minSize &&
3417+ Math.abs(selection.second.y - selection.first.y) >= minSize;
3418+ }
3419+
3420+ plot.clearSelection = clearSelection;
3421+ plot.setSelection = setSelection;
3422+ plot.getSelection = getSelection;
3423+
3424+ plot.hooks.bindEvents.push(function(plot, eventHolder) {
3425+ var o = plot.getOptions();
3426+ if (o.selection.mode != null)
3427+ eventHolder.mousemove(onMouseMove);
3428+
3429+ if (o.selection.mode != null)
3430+ eventHolder.mousedown(onMouseDown);
3431+ });
3432+
3433+
3434+ plot.hooks.drawOverlay.push(function (plot, ctx) {
3435+ // draw selection
3436+ if (selection.show && selectionIsSane()) {
3437+ var plotOffset = plot.getPlotOffset();
3438+ var o = plot.getOptions();
3439+
3440+ ctx.save();
3441+ ctx.translate(plotOffset.left, plotOffset.top);
3442+
3443+ var c = $.color.parse(o.selection.color);
3444+
3445+ ctx.strokeStyle = c.scale('a', 0.8).toString();
3446+ ctx.lineWidth = 1;
3447+ ctx.lineJoin = "round";
3448+ ctx.fillStyle = c.scale('a', 0.4).toString();
3449+
3450+ var x = Math.min(selection.first.x, selection.second.x),
3451+ y = Math.min(selection.first.y, selection.second.y),
3452+ w = Math.abs(selection.second.x - selection.first.x),
3453+ h = Math.abs(selection.second.y - selection.first.y);
3454+
3455+ ctx.fillRect(x, y, w, h);
3456+ ctx.strokeRect(x, y, w, h);
3457+
3458+ ctx.restore();
3459+ }
3460+ });
3461+ }
3462+
3463+ $.plot.plugins.push({
3464+ init: init,
3465+ options: {
3466+ selection: {
3467+ mode: null, // one of null, "x", "y" or "xy"
3468+ color: "#e8cfac"
3469+ }
3470+ },
3471+ name: 'selection',
3472+ version: '1.0'
3473+ });
3474+})(jQuery);
3475
3476=== added file 'webapp/content/js/jquery.graphite.js'
3477--- webapp/content/js/jquery.graphite.js 1970-01-01 00:00:00 +0000
3478+++ webapp/content/js/jquery.graphite.js 2010-11-01 18:48:50 +0000
3479@@ -0,0 +1,383 @@
3480+(function( $ ) {
3481+ $.fn.editable_in_place = function(callback) {
3482+ var editable = $(this);
3483+ if (editable.length > 1) {
3484+ console.error("Call $().editable_in_place only on a singular jquery object.");
3485+ }
3486+
3487+ var editing = false;
3488+
3489+ editable.bind('click', function () {
3490+ var $element = this;
3491+
3492+ if (editing == true) return;
3493+
3494+ editing = true;
3495+
3496+ var $edit = $('<input type="text" class="edit_in_place" value="' + editable.text() + '"/>');
3497+
3498+ $edit.css({'height' : editable.height(), 'width' : editable.width()});
3499+ editable.hide();
3500+ editable.after($edit);
3501+ $edit.focus();
3502+
3503+ $edit.bind('blur', function() { // on blur, forget edits and reset.
3504+ $edit.remove();
3505+ editable.show();
3506+ editing = false;
3507+ });
3508+
3509+ $edit.keydown(function(e) {
3510+ if(e.which===27)$edit.blur(); // blur on Esc: see above
3511+ if(e.which===13 || e.which===9) { // Enter or Tab: run the callback with the value
3512+ e.preventDefault();
3513+ $edit.hide();
3514+ editable.show();
3515+ if($edit.val()!=='') {
3516+ editing = false;
3517+ callback($element, $edit.val());
3518+ }
3519+ $edit.remove();
3520+ }
3521+ });
3522+ });
3523+ };
3524+
3525+ $.fn.graphiteGraph = function() {
3526+ return this.each(function() {
3527+
3528+ var graph = $(this);
3529+ var plot = null;
3530+ var graph_lines = {};
3531+ var metric_yaxis = {};
3532+ var xaxisranges = {};
3533+ var yaxisranges = {};
3534+ var legends = null;
3535+ var updateLegendTimeout = null;
3536+ var latestPosition = null;
3537+ var autocompleteoptions = {
3538+ minChars: 0,
3539+ selectFirst: false,
3540+ };
3541+
3542+ var parse_incoming = function(incoming_data) {
3543+ var result = [];
3544+ var start = incoming_data.start;
3545+ var end = incoming_data.end;
3546+ var step = incoming_data.step;
3547+
3548+ for (i in incoming_data.data) {
3549+ result.push([(start+step*i)*1000, incoming_data.data[i]]);
3550+
3551+ }
3552+ return {
3553+ label: incoming_data.name,
3554+ data: result,
3555+ lines: {show: true, fill: false}
3556+ };
3557+ };
3558+
3559+
3560+ var render = function () {
3561+ var lines = []
3562+ for (i in graph_lines) {
3563+ for (j in graph_lines[i]) {
3564+ var newline = $.extend({}, graph_lines[i][j]);
3565+ if (metric_yaxis[i] == "two") {
3566+ newline['yaxis'] = 2;
3567+ }
3568+ lines.push(newline);
3569+ }
3570+ }
3571+ var xaxismode = { mode: "time" };
3572+ var yaxismode = { };
3573+
3574+ $.extend(xaxismode, xaxisranges);
3575+ $.extend(yaxismode, yaxisranges);
3576+
3577+ plot = $.plot($("#graph"),
3578+ lines,
3579+ {
3580+ xaxis: xaxismode,
3581+ yaxis: yaxismode,
3582+ grid: { hoverable: true, },
3583+ selection: { mode: "xy" },
3584+ legend: { show: true, container: graph.find("#legend") },
3585+ crosshair: { mode: "x" },
3586+ }
3587+ );
3588+
3589+
3590+ for (i in lines) {
3591+ lines[i] = $.extend({}, lines[i]);
3592+ lines[i].label = null;
3593+ }
3594+ var overview = $.plot($("#overview"),
3595+ lines,
3596+ {
3597+ xaxis: { mode: "time" },
3598+ selection: { mode: "x" },
3599+ }
3600+ );
3601+
3602+ // legends magic
3603+ legends = graph.find(".legendLabel");
3604+ // update link
3605+ graph.find("#graphurl").attr("href", build_full_url());
3606+
3607+ }
3608+
3609+ function updateLegend() {
3610+ updateLegendTimeout = null;
3611+
3612+ var pos = latestPosition;
3613+
3614+ var axes = plot.getAxes();
3615+ if (pos.x < axes.xaxis.min || pos.x > axes.xaxis.max ||
3616+ pos.y < axes.yaxis.min || pos.y > axes.yaxis.max) {
3617+ var i, j, dataset = plot.getData();
3618+ for (i = 0; i < dataset.length; ++i) {
3619+ var series = dataset[i];
3620+ legends.eq(i).text(series.label);
3621+ }
3622+ }
3623+
3624+ var i, j, dataset = plot.getData();
3625+ for (i = 0; i < dataset.length; ++i) {
3626+ var series = dataset[i];
3627+
3628+ // find the nearest points, x-wise
3629+ for (j = 0; j < series.data.length; ++j)
3630+ if (series.data[j][0] > pos.x)
3631+ break;
3632+
3633+ // now interpolate
3634+ var y, p1 = series.data[j - 1], p2 = series.data[j];
3635+ if (p1 == null)
3636+ y = p2[1];
3637+ else if (p2 == null)
3638+ y = p1[1];
3639+ else
3640+ y = p1[1] + (p2[1] - p1[1]) * (pos.x - p1[0]) / (p2[0] - p1[0]);
3641+
3642+ if ( y != null ) {
3643+ legends.eq(i).text(series.label + " = " + y.toFixed(2));
3644+ } else {
3645+ legends.eq(i).text(series.label);
3646+ }
3647+ legends.eq(i).css('width', legends.eq(i).width());
3648+ }
3649+ }
3650+
3651+ $("#graph").bind("plothover", function (event, pos, item) {
3652+ latestPosition = pos;
3653+ if (!updateLegendTimeout)
3654+ updateLegendTimeout = setTimeout(updateLegend, 50);
3655+ });
3656+
3657+ function showTooltip(x, y, contents) {
3658+ $('<div id="tooltip">' + contents + '</div>').css( {
3659+ position: 'absolute',
3660+ display: 'none',
3661+ top: y + 5,
3662+ left: x + 5,
3663+ border: '1px solid #fdd',
3664+ padding: '2px',
3665+ 'background-color': '#fee',
3666+ opacity: 0.80
3667+ }).appendTo("body").fadeIn(200);
3668+ }
3669+
3670+ var previousPoint = null;
3671+ $("#graph").bind("plothover", function (event, pos, item) {
3672+ if (item) {
3673+ if (previousPoint != item.datapoint) {
3674+ previousPoint = item.datapoint;
3675+
3676+ $("#tooltip").remove();
3677+ var x = item.datapoint[0].toFixed(2),
3678+ y = item.datapoint[1].toFixed(2);
3679+
3680+ showTooltip(item.pageX, item.pageY,
3681+ item.series.label + " = " + y);
3682+ }
3683+ }
3684+ else {
3685+ $("#tooltip").remove();
3686+ previousPoint = null;
3687+ }
3688+ });
3689+
3690+ $("#overview").bind("plotselected", function (event, ranges) {
3691+ xaxisranges = { min: ranges.xaxis.from, max: ranges.xaxis.to };
3692+ yaxisranges = { min: ranges.yaxis.from, max: ranges.yaxis.to };
3693+ render()
3694+ });
3695+
3696+ $("#graph").bind("plotselected", function (event, ranges) {
3697+ xaxisranges = { min: ranges.xaxis.from, max: ranges.xaxis.to };
3698+ yaxisranges = { min: ranges.yaxis.from, max: ranges.yaxis.to };
3699+ render()
3700+ });
3701+
3702+ var clear_zoom = function () {
3703+ xaxisranges = {};
3704+ yaxisranges = {};
3705+ render();
3706+ }
3707+
3708+ var recalculate_all = function () {
3709+ graph.find('.metricrow').each(function () {
3710+ var metric = $(this);
3711+ update_metric_row(metric);
3712+ });
3713+ render();
3714+ }
3715+
3716+ var build_full_url = function() {
3717+ var url = window.location.protocol + '//' +
3718+ window.location.host + window.location.pathname +
3719+ '?' + build_when();
3720+ for (series in graph_lines) {
3721+ if (metric_yaxis[series] == "two") {
3722+ url = url + '&y2target=' + series;
3723+ } else {
3724+ url = url + '&target=' + series;
3725+ }
3726+ }
3727+ return url;
3728+ }
3729+
3730+ var build_when = function () {
3731+ var when = '';
3732+ var from = graph.find("#from").text();
3733+ if (from) {
3734+ when = when + '&from=' + from;
3735+ }
3736+ var until = graph.find("#until").text();
3737+ if (until) {
3738+ when = when + '&until=' + until;
3739+ }
3740+ return when
3741+ }
3742+ var build_url = function (series) {
3743+ when = build_when()
3744+ return 'rawdata?'+when+'&target='+series;
3745+ }
3746+
3747+ var update_metric_row = function(metric_row) {
3748+ var metric = $(metric_row);
3749+ var metric_name = metric.find(".metricname").text();
3750+ metric.find(".metricname").addClass("ajaxworking");
3751+ metric_yaxis[metric_name] = metric.find(".yaxis").text();
3752+
3753+ $.ajax({
3754+ url: build_url(metric_name),
3755+ success: function(req_data) {
3756+ metric.find(".metricname").removeClass("ajaxerror");
3757+ metric.find(".metricname").removeClass("ajaxworking");
3758+ graph_lines[metric_name] = [];
3759+ target = graph_lines[metric_name];
3760+ for (i in req_data) {
3761+ target.push(parse_incoming(req_data[i]));
3762+ }
3763+ render();
3764+ },
3765+ error: function(req, status, err) {
3766+ metric.find(".metricname").removeClass("ajaxworking");
3767+ metric.find(".metricname").addClass("ajaxerror");
3768+ render();
3769+ }
3770+ });
3771+
3772+
3773+ }
3774+
3775+ // configure the date boxes
3776+ graph.find('#from').editable_in_place(
3777+ function(editable, value) {
3778+ $(editable).text(value);
3779+ recalculate_all();
3780+ }
3781+ );
3782+
3783+
3784+ graph.find('#until').editable_in_place(
3785+ function(editable, value) {
3786+ $(editable).text(value);
3787+ recalculate_all();
3788+ }
3789+ );
3790+
3791+ graph.find('#update').bind('click',
3792+ function() {
3793+ recalculate_all();
3794+ }
3795+ );
3796+
3797+ graph.find('#clearzoom').bind('click',
3798+ clear_zoom
3799+ );
3800+
3801+ // configure metricrows
3802+ var setup_row = function (metric) {
3803+ var metric_name = metric.find('.metricname').text();
3804+
3805+ metric.find('.metricname').editable_in_place(
3806+ function(editable, value) {
3807+ delete graph_lines[$(editable).text()];
3808+ $(editable).text(value);
3809+ update_metric_row(metric);
3810+ }
3811+ );
3812+ metric.find('.killrow').bind('click', function() {
3813+ delete graph_lines[metric.find('.metricname').text()];
3814+ metric.remove();
3815+ render();
3816+ });
3817+
3818+ metric.find('.yaxis').bind('click', function() {
3819+ if ($(this).text() == "one") {
3820+ $(this).text("two");
3821+ } else {
3822+ $(this).text("one");
3823+ }
3824+ metric_yaxis[metric_name] = metric.find(".yaxis").text();
3825+ render();
3826+ });
3827+ }
3828+
3829+ graph.find('.metricrow').each(function() {
3830+ setup_row($(this));
3831+ });
3832+
3833+ graph.find('.metricrow').each(function() {
3834+ var row = $(this);
3835+
3836+ });
3837+ // configure new metric input
3838+ graph.find('#newmetric').each(function () {
3839+ var edit = $(this);
3840+ edit.autocomplete('findmetric', autocompleteoptions);
3841+ edit.keydown(function(e) {
3842+ if(e.which===13) { // on enter
3843+ // add row
3844+ edit.blur();
3845+ if (graph_lines[edit.val()] == null) {
3846+ var new_row = $('<tr class="metricrow"><td><a href=#><span class="metricName">'+edit.val()+'</span></a></td><td><a href=#><span class="yaxis">one</span></a></td><td class="killrow"><img src="../content/img/delete.png"></td></tr>');
3847+ setup_row(new_row);
3848+ graph.find('#newmetricrow').before(new_row);
3849+ update_metric_row(new_row);
3850+ // clear input
3851+ }
3852+ edit.val('');
3853+ }
3854+ });
3855+ });
3856+
3857+ // get data
3858+ recalculate_all();
3859+ });
3860+ };
3861+
3862+})( jQuery );
3863\ No newline at end of file
3864
3865=== added file 'webapp/content/js/jquery.js'
3866--- webapp/content/js/jquery.js 1970-01-01 00:00:00 +0000
3867+++ webapp/content/js/jquery.js 2010-11-01 18:48:50 +0000
3868@@ -0,0 +1,166 @@
3869+/*!
3870+ * jQuery JavaScript Library v1.4.3
3871+ * http://jquery.com/
3872+ *
3873+ * Copyright 2010, John Resig
3874+ * Dual licensed under the MIT or GPL Version 2 licenses.
3875+ * http://jquery.org/license
3876+ *
3877+ * Includes Sizzle.js
3878+ * http://sizzlejs.com/
3879+ * Copyright 2010, The Dojo Foundation
3880+ * Released under the MIT, BSD, and GPL Licenses.
3881+ *
3882+ * Date: Thu Oct 14 23:10:06 2010 -0400
3883+ */
3884+(function(E,A){function U(){return false}function ba(){return true}function ja(a,b,d){d[0].type=a;return c.event.handle.apply(b,d)}function Ga(a){var b,d,e=[],f=[],h,k,l,n,s,v,B,D;k=c.data(this,this.nodeType?"events":"__events__");if(typeof k==="function")k=k.events;if(!(a.liveFired===this||!k||!k.live||a.button&&a.type==="click")){if(a.namespace)D=RegExp("(^|\\.)"+a.namespace.split(".").join("\\.(?:.*\\.)?")+"(\\.|$)");a.liveFired=this;var H=k.live.slice(0);for(n=0;n<H.length;n++){k=H[n];k.origType.replace(X,
3885+"")===a.type?f.push(k.selector):H.splice(n--,1)}f=c(a.target).closest(f,a.currentTarget);s=0;for(v=f.length;s<v;s++){B=f[s];for(n=0;n<H.length;n++){k=H[n];if(B.selector===k.selector&&(!D||D.test(k.namespace))){l=B.elem;h=null;if(k.preType==="mouseenter"||k.preType==="mouseleave"){a.type=k.preType;h=c(a.relatedTarget).closest(k.selector)[0]}if(!h||h!==l)e.push({elem:l,handleObj:k,level:B.level})}}}s=0;for(v=e.length;s<v;s++){f=e[s];if(d&&f.level>d)break;a.currentTarget=f.elem;a.data=f.handleObj.data;
3886+a.handleObj=f.handleObj;D=f.handleObj.origHandler.apply(f.elem,arguments);if(D===false||a.isPropagationStopped()){d=f.level;if(D===false)b=false}}return b}}function Y(a,b){return(a&&a!=="*"?a+".":"")+b.replace(Ha,"`").replace(Ia,"&")}function ka(a,b,d){if(c.isFunction(b))return c.grep(a,function(f,h){return!!b.call(f,h,f)===d});else if(b.nodeType)return c.grep(a,function(f){return f===b===d});else if(typeof b==="string"){var e=c.grep(a,function(f){return f.nodeType===1});if(Ja.test(b))return c.filter(b,
3887+e,!d);else b=c.filter(b,e)}return c.grep(a,function(f){return c.inArray(f,b)>=0===d})}function la(a,b){var d=0;b.each(function(){if(this.nodeName===(a[d]&&a[d].nodeName)){var e=c.data(a[d++]),f=c.data(this,e);if(e=e&&e.events){delete f.handle;f.events={};for(var h in e)for(var k in e[h])c.event.add(this,h,e[h][k],e[h][k].data)}}})}function Ka(a,b){b.src?c.ajax({url:b.src,async:false,dataType:"script"}):c.globalEval(b.text||b.textContent||b.innerHTML||"");b.parentNode&&b.parentNode.removeChild(b)}
3888+function ma(a,b,d){var e=b==="width"?a.offsetWidth:a.offsetHeight;if(d==="border")return e;c.each(b==="width"?La:Ma,function(){d||(e-=parseFloat(c.css(a,"padding"+this))||0);if(d==="margin")e+=parseFloat(c.css(a,"margin"+this))||0;else e-=parseFloat(c.css(a,"border"+this+"Width"))||0});return e}function ca(a,b,d,e){if(c.isArray(b)&&b.length)c.each(b,function(f,h){d||Na.test(a)?e(a,h):ca(a+"["+(typeof h==="object"||c.isArray(h)?f:"")+"]",h,d,e)});else if(!d&&b!=null&&typeof b==="object")c.isEmptyObject(b)?
3889+e(a,""):c.each(b,function(f,h){ca(a+"["+f+"]",h,d,e)});else e(a,b)}function S(a,b){var d={};c.each(na.concat.apply([],na.slice(0,b)),function(){d[this]=a});return d}function oa(a){if(!da[a]){var b=c("<"+a+">").appendTo("body"),d=b.css("display");b.remove();if(d==="none"||d==="")d="block";da[a]=d}return da[a]}function ea(a){return c.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:false}var u=E.document,c=function(){function a(){if(!b.isReady){try{u.documentElement.doScroll("left")}catch(i){setTimeout(a,
3890+1);return}b.ready()}}var b=function(i,r){return new b.fn.init(i,r)},d=E.jQuery,e=E.$,f,h=/^(?:[^<]*(<[\w\W]+>)[^>]*$|#([\w\-]+)$)/,k=/\S/,l=/^\s+/,n=/\s+$/,s=/\W/,v=/\d/,B=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,D=/^[\],:{}\s]*$/,H=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,w=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,G=/(?:^|:|,)(?:\s*\[)+/g,M=/(webkit)[ \/]([\w.]+)/,g=/(opera)(?:.*version)?[ \/]([\w.]+)/,j=/(msie) ([\w.]+)/,o=/(mozilla)(?:.*? rv:([\w.]+))?/,m=navigator.userAgent,p=false,
3891+q=[],t,x=Object.prototype.toString,C=Object.prototype.hasOwnProperty,P=Array.prototype.push,N=Array.prototype.slice,R=String.prototype.trim,Q=Array.prototype.indexOf,L={};b.fn=b.prototype={init:function(i,r){var y,z,F;if(!i)return this;if(i.nodeType){this.context=this[0]=i;this.length=1;return this}if(i==="body"&&!r&&u.body){this.context=u;this[0]=u.body;this.selector="body";this.length=1;return this}if(typeof i==="string")if((y=h.exec(i))&&(y[1]||!r))if(y[1]){F=r?r.ownerDocument||r:u;if(z=B.exec(i))if(b.isPlainObject(r)){i=
3892+[u.createElement(z[1])];b.fn.attr.call(i,r,true)}else i=[F.createElement(z[1])];else{z=b.buildFragment([y[1]],[F]);i=(z.cacheable?z.fragment.cloneNode(true):z.fragment).childNodes}return b.merge(this,i)}else{if((z=u.getElementById(y[2]))&&z.parentNode){if(z.id!==y[2])return f.find(i);this.length=1;this[0]=z}this.context=u;this.selector=i;return this}else if(!r&&!s.test(i)){this.selector=i;this.context=u;i=u.getElementsByTagName(i);return b.merge(this,i)}else return!r||r.jquery?(r||f).find(i):b(r).find(i);
3893+else if(b.isFunction(i))return f.ready(i);if(i.selector!==A){this.selector=i.selector;this.context=i.context}return b.makeArray(i,this)},selector:"",jquery:"1.4.3",length:0,size:function(){return this.length},toArray:function(){return N.call(this,0)},get:function(i){return i==null?this.toArray():i<0?this.slice(i)[0]:this[i]},pushStack:function(i,r,y){var z=b();b.isArray(i)?P.apply(z,i):b.merge(z,i);z.prevObject=this;z.context=this.context;if(r==="find")z.selector=this.selector+(this.selector?" ":
3894+"")+y;else if(r)z.selector=this.selector+"."+r+"("+y+")";return z},each:function(i,r){return b.each(this,i,r)},ready:function(i){b.bindReady();if(b.isReady)i.call(u,b);else q&&q.push(i);return this},eq:function(i){return i===-1?this.slice(i):this.slice(i,+i+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(N.apply(this,arguments),"slice",N.call(arguments).join(","))},map:function(i){return this.pushStack(b.map(this,function(r,y){return i.call(r,
3895+y,r)}))},end:function(){return this.prevObject||b(null)},push:P,sort:[].sort,splice:[].splice};b.fn.init.prototype=b.fn;b.extend=b.fn.extend=function(){var i=arguments[0]||{},r=1,y=arguments.length,z=false,F,I,K,J,fa;if(typeof i==="boolean"){z=i;i=arguments[1]||{};r=2}if(typeof i!=="object"&&!b.isFunction(i))i={};if(y===r){i=this;--r}for(;r<y;r++)if((F=arguments[r])!=null)for(I in F){K=i[I];J=F[I];if(i!==J)if(z&&J&&(b.isPlainObject(J)||(fa=b.isArray(J)))){if(fa){fa=false;clone=K&&b.isArray(K)?K:[]}else clone=
3896+K&&b.isPlainObject(K)?K:{};i[I]=b.extend(z,clone,J)}else if(J!==A)i[I]=J}return i};b.extend({noConflict:function(i){E.$=e;if(i)E.jQuery=d;return b},isReady:false,readyWait:1,ready:function(i){i===true&&b.readyWait--;if(!b.readyWait||i!==true&&!b.isReady){if(!u.body)return setTimeout(b.ready,1);b.isReady=true;if(!(i!==true&&--b.readyWait>0)){if(q){for(var r=0;i=q[r++];)i.call(u,b);q=null}b.fn.triggerHandler&&b(u).triggerHandler("ready")}}},bindReady:function(){if(!p){p=true;if(u.readyState==="complete")return setTimeout(b.ready,
3897+1);if(u.addEventListener){u.addEventListener("DOMContentLoaded",t,false);E.addEventListener("load",b.ready,false)}else if(u.attachEvent){u.attachEvent("onreadystatechange",t);E.attachEvent("onload",b.ready);var i=false;try{i=E.frameElement==null}catch(r){}u.documentElement.doScroll&&i&&a()}}},isFunction:function(i){return b.type(i)==="function"},isArray:Array.isArray||function(i){return b.type(i)==="array"},isWindow:function(i){return i&&typeof i==="object"&&"setInterval"in i},isNaN:function(i){return i==
3898+null||!v.test(i)||isNaN(i)},type:function(i){return i==null?String(i):L[x.call(i)]||"object"},isPlainObject:function(i){if(!i||b.type(i)!=="object"||i.nodeType||b.isWindow(i))return false;if(i.constructor&&!C.call(i,"constructor")&&!C.call(i.constructor.prototype,"isPrototypeOf"))return false;for(var r in i);return r===A||C.call(i,r)},isEmptyObject:function(i){for(var r in i)return false;return true},error:function(i){throw i;},parseJSON:function(i){if(typeof i!=="string"||!i)return null;i=b.trim(i);
3899+if(D.test(i.replace(H,"@").replace(w,"]").replace(G,"")))return E.JSON&&E.JSON.parse?E.JSON.parse(i):(new Function("return "+i))();else b.error("Invalid JSON: "+i)},noop:function(){},globalEval:function(i){if(i&&k.test(i)){var r=u.getElementsByTagName("head")[0]||u.documentElement,y=u.createElement("script");y.type="text/javascript";if(b.support.scriptEval)y.appendChild(u.createTextNode(i));else y.text=i;r.insertBefore(y,r.firstChild);r.removeChild(y)}},nodeName:function(i,r){return i.nodeName&&i.nodeName.toUpperCase()===
3900+r.toUpperCase()},each:function(i,r,y){var z,F=0,I=i.length,K=I===A||b.isFunction(i);if(y)if(K)for(z in i){if(r.apply(i[z],y)===false)break}else for(;F<I;){if(r.apply(i[F++],y)===false)break}else if(K)for(z in i){if(r.call(i[z],z,i[z])===false)break}else for(y=i[0];F<I&&r.call(y,F,y)!==false;y=i[++F]);return i},trim:R?function(i){return i==null?"":R.call(i)}:function(i){return i==null?"":i.toString().replace(l,"").replace(n,"")},makeArray:function(i,r){var y=r||[];if(i!=null){var z=b.type(i);i.length==
3901+null||z==="string"||z==="function"||z==="regexp"||b.isWindow(i)?P.call(y,i):b.merge(y,i)}return y},inArray:function(i,r){if(r.indexOf)return r.indexOf(i);for(var y=0,z=r.length;y<z;y++)if(r[y]===i)return y;return-1},merge:function(i,r){var y=i.length,z=0;if(typeof r.length==="number")for(var F=r.length;z<F;z++)i[y++]=r[z];else for(;r[z]!==A;)i[y++]=r[z++];i.length=y;return i},grep:function(i,r,y){var z=[],F;y=!!y;for(var I=0,K=i.length;I<K;I++){F=!!r(i[I],I);y!==F&&z.push(i[I])}return z},map:function(i,
3902+r,y){for(var z=[],F,I=0,K=i.length;I<K;I++){F=r(i[I],I,y);if(F!=null)z[z.length]=F}return z.concat.apply([],z)},guid:1,proxy:function(i,r,y){if(arguments.length===2)if(typeof r==="string"){y=i;i=y[r];r=A}else if(r&&!b.isFunction(r)){y=r;r=A}if(!r&&i)r=function(){return i.apply(y||this,arguments)};if(i)r.guid=i.guid=i.guid||r.guid||b.guid++;return r},access:function(i,r,y,z,F,I){var K=i.length;if(typeof r==="object"){for(var J in r)b.access(i,J,r[J],z,F,y);return i}if(y!==A){z=!I&&z&&b.isFunction(y);
3903+for(J=0;J<K;J++)F(i[J],r,z?y.call(i[J],J,F(i[J],r)):y,I);return i}return K?F(i[0],r):A},now:function(){return(new Date).getTime()},uaMatch:function(i){i=i.toLowerCase();i=M.exec(i)||g.exec(i)||j.exec(i)||i.indexOf("compatible")<0&&o.exec(i)||[];return{browser:i[1]||"",version:i[2]||"0"}},browser:{}});b.each("Boolean Number String Function Array Date RegExp Object".split(" "),function(i,r){L["[object "+r+"]"]=r.toLowerCase()});m=b.uaMatch(m);if(m.browser){b.browser[m.browser]=true;b.browser.version=
3904+m.version}if(b.browser.webkit)b.browser.safari=true;if(Q)b.inArray=function(i,r){return Q.call(r,i)};if(!/\s/.test("\u00a0")){l=/^[\s\xA0]+/;n=/[\s\xA0]+$/}f=b(u);if(u.addEventListener)t=function(){u.removeEventListener("DOMContentLoaded",t,false);b.ready()};else if(u.attachEvent)t=function(){if(u.readyState==="complete"){u.detachEvent("onreadystatechange",t);b.ready()}};return E.jQuery=E.$=b}();(function(){c.support={};var a=u.documentElement,b=u.createElement("script"),d=u.createElement("div"),
3905+e="script"+c.now();d.style.display="none";d.innerHTML=" <link/><table></table><a href='/a' style='color:red;float:left;opacity:.55;'>a</a><input type='checkbox'/>";var f=d.getElementsByTagName("*"),h=d.getElementsByTagName("a")[0],k=u.createElement("select"),l=k.appendChild(u.createElement("option"));if(!(!f||!f.length||!h)){c.support={leadingWhitespace:d.firstChild.nodeType===3,tbody:!d.getElementsByTagName("tbody").length,htmlSerialize:!!d.getElementsByTagName("link").length,style:/red/.test(h.getAttribute("style")),
3906+hrefNormalized:h.getAttribute("href")==="/a",opacity:/^0.55$/.test(h.style.opacity),cssFloat:!!h.style.cssFloat,checkOn:d.getElementsByTagName("input")[0].value==="on",optSelected:l.selected,optDisabled:false,checkClone:false,scriptEval:false,noCloneEvent:true,boxModel:null,inlineBlockNeedsLayout:false,shrinkWrapBlocks:false,reliableHiddenOffsets:true};k.disabled=true;c.support.optDisabled=!l.disabled;b.type="text/javascript";try{b.appendChild(u.createTextNode("window."+e+"=1;"))}catch(n){}a.insertBefore(b,
3907+a.firstChild);if(E[e]){c.support.scriptEval=true;delete E[e]}a.removeChild(b);if(d.attachEvent&&d.fireEvent){d.attachEvent("onclick",function s(){c.support.noCloneEvent=false;d.detachEvent("onclick",s)});d.cloneNode(true).fireEvent("onclick")}d=u.createElement("div");d.innerHTML="<input type='radio' name='radiotest' checked='checked'/>";a=u.createDocumentFragment();a.appendChild(d.firstChild);c.support.checkClone=a.cloneNode(true).cloneNode(true).lastChild.checked;c(function(){var s=u.createElement("div");
3908+s.style.width=s.style.paddingLeft="1px";u.body.appendChild(s);c.boxModel=c.support.boxModel=s.offsetWidth===2;if("zoom"in s.style){s.style.display="inline";s.style.zoom=1;c.support.inlineBlockNeedsLayout=s.offsetWidth===2;s.style.display="";s.innerHTML="<div style='width:4px;'></div>";c.support.shrinkWrapBlocks=s.offsetWidth!==2}s.innerHTML="<table><tr><td style='padding:0;display:none'></td><td>t</td></tr></table>";var v=s.getElementsByTagName("td");c.support.reliableHiddenOffsets=v[0].offsetHeight===
3909+0;v[0].style.display="";v[1].style.display="none";c.support.reliableHiddenOffsets=c.support.reliableHiddenOffsets&&v[0].offsetHeight===0;s.innerHTML="";u.body.removeChild(s).style.display="none"});a=function(s){var v=u.createElement("div");s="on"+s;var B=s in v;if(!B){v.setAttribute(s,"return;");B=typeof v[s]==="function"}return B};c.support.submitBubbles=a("submit");c.support.changeBubbles=a("change");a=b=d=f=h=null}})();c.props={"for":"htmlFor","class":"className",readonly:"readOnly",maxlength:"maxLength",
3910+cellspacing:"cellSpacing",rowspan:"rowSpan",colspan:"colSpan",tabindex:"tabIndex",usemap:"useMap",frameborder:"frameBorder"};var pa={},Oa=/^(?:\{.*\}|\[.*\])$/;c.extend({cache:{},uuid:0,expando:"jQuery"+c.now(),noData:{embed:true,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:true},data:function(a,b,d){if(c.acceptData(a)){a=a==E?pa:a;var e=a.nodeType,f=e?a[c.expando]:null,h=c.cache;if(!(e&&!f&&typeof b==="string"&&d===A)){if(e)f||(a[c.expando]=f=++c.uuid);else h=a;if(typeof b==="object")if(e)h[f]=
3911+c.extend(h[f],b);else c.extend(h,b);else if(e&&!h[f])h[f]={};a=e?h[f]:h;if(d!==A)a[b]=d;return typeof b==="string"?a[b]:a}}},removeData:function(a,b){if(c.acceptData(a)){a=a==E?pa:a;var d=a.nodeType,e=d?a[c.expando]:a,f=c.cache,h=d?f[e]:e;if(b){if(h){delete h[b];d&&c.isEmptyObject(h)&&c.removeData(a)}}else if(d&&c.support.deleteExpando)delete a[c.expando];else if(a.removeAttribute)a.removeAttribute(c.expando);else if(d)delete f[e];else for(var k in a)delete a[k]}},acceptData:function(a){if(a.nodeName){var b=
3912+c.noData[a.nodeName.toLowerCase()];if(b)return!(b===true||a.getAttribute("classid")!==b)}return true}});c.fn.extend({data:function(a,b){if(typeof a==="undefined")return this.length?c.data(this[0]):null;else if(typeof a==="object")return this.each(function(){c.data(this,a)});var d=a.split(".");d[1]=d[1]?"."+d[1]:"";if(b===A){var e=this.triggerHandler("getData"+d[1]+"!",[d[0]]);if(e===A&&this.length){e=c.data(this[0],a);if(e===A&&this[0].nodeType===1){e=this[0].getAttribute("data-"+a);if(typeof e===
3913+"string")try{e=e==="true"?true:e==="false"?false:e==="null"?null:!c.isNaN(e)?parseFloat(e):Oa.test(e)?c.parseJSON(e):e}catch(f){}else e=A}}return e===A&&d[1]?this.data(d[0]):e}else return this.each(function(){var h=c(this),k=[d[0],b];h.triggerHandler("setData"+d[1]+"!",k);c.data(this,a,b);h.triggerHandler("changeData"+d[1]+"!",k)})},removeData:function(a){return this.each(function(){c.removeData(this,a)})}});c.extend({queue:function(a,b,d){if(a){b=(b||"fx")+"queue";var e=c.data(a,b);if(!d)return e||
3914+[];if(!e||c.isArray(d))e=c.data(a,b,c.makeArray(d));else e.push(d);return e}},dequeue:function(a,b){b=b||"fx";var d=c.queue(a,b),e=d.shift();if(e==="inprogress")e=d.shift();if(e){b==="fx"&&d.unshift("inprogress");e.call(a,function(){c.dequeue(a,b)})}}});c.fn.extend({queue:function(a,b){if(typeof a!=="string"){b=a;a="fx"}if(b===A)return c.queue(this[0],a);return this.each(function(){var d=c.queue(this,a,b);a==="fx"&&d[0]!=="inprogress"&&c.dequeue(this,a)})},dequeue:function(a){return this.each(function(){c.dequeue(this,
3915+a)})},delay:function(a,b){a=c.fx?c.fx.speeds[a]||a:a;b=b||"fx";return this.queue(b,function(){var d=this;setTimeout(function(){c.dequeue(d,b)},a)})},clearQueue:function(a){return this.queue(a||"fx",[])}});var qa=/[\n\t]/g,ga=/\s+/,Pa=/\r/g,Qa=/^(?:href|src|style)$/,Ra=/^(?:button|input)$/i,Sa=/^(?:button|input|object|select|textarea)$/i,Ta=/^a(?:rea)?$/i,ra=/^(?:radio|checkbox)$/i;c.fn.extend({attr:function(a,b){return c.access(this,a,b,true,c.attr)},removeAttr:function(a){return this.each(function(){c.attr(this,
3916+a,"");this.nodeType===1&&this.removeAttribute(a)})},addClass:function(a){if(c.isFunction(a))return this.each(function(s){var v=c(this);v.addClass(a.call(this,s,v.attr("class")))});if(a&&typeof a==="string")for(var b=(a||"").split(ga),d=0,e=this.length;d<e;d++){var f=this[d];if(f.nodeType===1)if(f.className){for(var h=" "+f.className+" ",k=f.className,l=0,n=b.length;l<n;l++)if(h.indexOf(" "+b[l]+" ")<0)k+=" "+b[l];f.className=c.trim(k)}else f.className=a}return this},removeClass:function(a){if(c.isFunction(a))return this.each(function(n){var s=
3917+c(this);s.removeClass(a.call(this,n,s.attr("class")))});if(a&&typeof a==="string"||a===A)for(var b=(a||"").split(ga),d=0,e=this.length;d<e;d++){var f=this[d];if(f.nodeType===1&&f.className)if(a){for(var h=(" "+f.className+" ").replace(qa," "),k=0,l=b.length;k<l;k++)h=h.replace(" "+b[k]+" "," ");f.className=c.trim(h)}else f.className=""}return this},toggleClass:function(a,b){var d=typeof a,e=typeof b==="boolean";if(c.isFunction(a))return this.each(function(f){var h=c(this);h.toggleClass(a.call(this,
3918+f,h.attr("class"),b),b)});return this.each(function(){if(d==="string")for(var f,h=0,k=c(this),l=b,n=a.split(ga);f=n[h++];){l=e?l:!k.hasClass(f);k[l?"addClass":"removeClass"](f)}else if(d==="undefined"||d==="boolean"){this.className&&c.data(this,"__className__",this.className);this.className=this.className||a===false?"":c.data(this,"__className__")||""}})},hasClass:function(a){a=" "+a+" ";for(var b=0,d=this.length;b<d;b++)if((" "+this[b].className+" ").replace(qa," ").indexOf(a)>-1)return true;return false},
3919+val:function(a){if(!arguments.length){var b=this[0];if(b){if(c.nodeName(b,"option")){var d=b.attributes.value;return!d||d.specified?b.value:b.text}if(c.nodeName(b,"select")){var e=b.selectedIndex;d=[];var f=b.options;b=b.type==="select-one";if(e<0)return null;var h=b?e:0;for(e=b?e+1:f.length;h<e;h++){var k=f[h];if(k.selected&&(c.support.optDisabled?!k.disabled:k.getAttribute("disabled")===null)&&(!k.parentNode.disabled||!c.nodeName(k.parentNode,"optgroup"))){a=c(k).val();if(b)return a;d.push(a)}}return d}if(ra.test(b.type)&&
3920+!c.support.checkOn)return b.getAttribute("value")===null?"on":b.value;return(b.value||"").replace(Pa,"")}return A}var l=c.isFunction(a);return this.each(function(n){var s=c(this),v=a;if(this.nodeType===1){if(l)v=a.call(this,n,s.val());if(v==null)v="";else if(typeof v==="number")v+="";else if(c.isArray(v))v=c.map(v,function(D){return D==null?"":D+""});if(c.isArray(v)&&ra.test(this.type))this.checked=c.inArray(s.val(),v)>=0;else if(c.nodeName(this,"select")){var B=c.makeArray(v);c("option",this).each(function(){this.selected=
3921+c.inArray(c(this).val(),B)>=0});if(!B.length)this.selectedIndex=-1}else this.value=v}})}});c.extend({attrFn:{val:true,css:true,html:true,text:true,data:true,width:true,height:true,offset:true},attr:function(a,b,d,e){if(!a||a.nodeType===3||a.nodeType===8)return A;if(e&&b in c.attrFn)return c(a)[b](d);e=a.nodeType!==1||!c.isXMLDoc(a);var f=d!==A;b=e&&c.props[b]||b;if(a.nodeType===1){var h=Qa.test(b);if((b in a||a[b]!==A)&&e&&!h){if(f){b==="type"&&Ra.test(a.nodeName)&&a.parentNode&&c.error("type property can't be changed");
3922+if(d===null)a.nodeType===1&&a.removeAttribute(b);else a[b]=d}if(c.nodeName(a,"form")&&a.getAttributeNode(b))return a.getAttributeNode(b).nodeValue;if(b==="tabIndex")return(b=a.getAttributeNode("tabIndex"))&&b.specified?b.value:Sa.test(a.nodeName)||Ta.test(a.nodeName)&&a.href?0:A;return a[b]}if(!c.support.style&&e&&b==="style"){if(f)a.style.cssText=""+d;return a.style.cssText}f&&a.setAttribute(b,""+d);if(!a.attributes[b]&&a.hasAttribute&&!a.hasAttribute(b))return A;a=!c.support.hrefNormalized&&e&&
3923+h?a.getAttribute(b,2):a.getAttribute(b);return a===null?A:a}}});var X=/\.(.*)$/,ha=/^(?:textarea|input|select)$/i,Ha=/\./g,Ia=/ /g,Ua=/[^\w\s.|`]/g,Va=function(a){return a.replace(Ua,"\\$&")},sa={focusin:0,focusout:0};c.event={add:function(a,b,d,e){if(!(a.nodeType===3||a.nodeType===8)){if(c.isWindow(a)&&a!==E&&!a.frameElement)a=E;if(d===false)d=U;var f,h;if(d.handler){f=d;d=f.handler}if(!d.guid)d.guid=c.guid++;if(h=c.data(a)){var k=a.nodeType?"events":"__events__",l=h[k],n=h.handle;if(typeof l===
3924+"function"){n=l.handle;l=l.events}else if(!l){a.nodeType||(h[k]=h=function(){});h.events=l={}}if(!n)h.handle=n=function(){return typeof c!=="undefined"&&!c.event.triggered?c.event.handle.apply(n.elem,arguments):A};n.elem=a;b=b.split(" ");for(var s=0,v;k=b[s++];){h=f?c.extend({},f):{handler:d,data:e};if(k.indexOf(".")>-1){v=k.split(".");k=v.shift();h.namespace=v.slice(0).sort().join(".")}else{v=[];h.namespace=""}h.type=k;if(!h.guid)h.guid=d.guid;var B=l[k],D=c.event.special[k]||{};if(!B){B=l[k]=[];
3925+if(!D.setup||D.setup.call(a,e,v,n)===false)if(a.addEventListener)a.addEventListener(k,n,false);else a.attachEvent&&a.attachEvent("on"+k,n)}if(D.add){D.add.call(a,h);if(!h.handler.guid)h.handler.guid=d.guid}B.push(h);c.event.global[k]=true}a=null}}},global:{},remove:function(a,b,d,e){if(!(a.nodeType===3||a.nodeType===8)){if(d===false)d=U;var f,h,k=0,l,n,s,v,B,D,H=a.nodeType?"events":"__events__",w=c.data(a),G=w&&w[H];if(w&&G){if(typeof G==="function"){w=G;G=G.events}if(b&&b.type){d=b.handler;b=b.type}if(!b||
3926+typeof b==="string"&&b.charAt(0)==="."){b=b||"";for(f in G)c.event.remove(a,f+b)}else{for(b=b.split(" ");f=b[k++];){v=f;l=f.indexOf(".")<0;n=[];if(!l){n=f.split(".");f=n.shift();s=RegExp("(^|\\.)"+c.map(n.slice(0).sort(),Va).join("\\.(?:.*\\.)?")+"(\\.|$)")}if(B=G[f])if(d){v=c.event.special[f]||{};for(h=e||0;h<B.length;h++){D=B[h];if(d.guid===D.guid){if(l||s.test(D.namespace)){e==null&&B.splice(h--,1);v.remove&&v.remove.call(a,D)}if(e!=null)break}}if(B.length===0||e!=null&&B.length===1){if(!v.teardown||
3927+v.teardown.call(a,n)===false)c.removeEvent(a,f,w.handle);delete G[f]}}else for(h=0;h<B.length;h++){D=B[h];if(l||s.test(D.namespace)){c.event.remove(a,v,D.handler,h);B.splice(h--,1)}}}if(c.isEmptyObject(G)){if(b=w.handle)b.elem=null;delete w.events;delete w.handle;if(typeof w==="function")c.removeData(a,H);else c.isEmptyObject(w)&&c.removeData(a)}}}}},trigger:function(a,b,d,e){var f=a.type||a;if(!e){a=typeof a==="object"?a[c.expando]?a:c.extend(c.Event(f),a):c.Event(f);if(f.indexOf("!")>=0){a.type=
3928+f=f.slice(0,-1);a.exclusive=true}if(!d){a.stopPropagation();c.event.global[f]&&c.each(c.cache,function(){this.events&&this.events[f]&&c.event.trigger(a,b,this.handle.elem)})}if(!d||d.nodeType===3||d.nodeType===8)return A;a.result=A;a.target=d;b=c.makeArray(b);b.unshift(a)}a.currentTarget=d;(e=d.nodeType?c.data(d,"handle"):(c.data(d,"__events__")||{}).handle)&&e.apply(d,b);e=d.parentNode||d.ownerDocument;try{if(!(d&&d.nodeName&&c.noData[d.nodeName.toLowerCase()]))if(d["on"+f]&&d["on"+f].apply(d,b)===
3929+false){a.result=false;a.preventDefault()}}catch(h){}if(!a.isPropagationStopped()&&e)c.event.trigger(a,b,e,true);else if(!a.isDefaultPrevented()){e=a.target;var k,l=f.replace(X,""),n=c.nodeName(e,"a")&&l==="click",s=c.event.special[l]||{};if((!s._default||s._default.call(d,a)===false)&&!n&&!(e&&e.nodeName&&c.noData[e.nodeName.toLowerCase()])){try{if(e[l]){if(k=e["on"+l])e["on"+l]=null;c.event.triggered=true;e[l]()}}catch(v){}if(k)e["on"+l]=k;c.event.triggered=false}}},handle:function(a){var b,d,e;
3930+d=[];var f,h=c.makeArray(arguments);a=h[0]=c.event.fix(a||E.event);a.currentTarget=this;b=a.type.indexOf(".")<0&&!a.exclusive;if(!b){e=a.type.split(".");a.type=e.shift();d=e.slice(0).sort();e=RegExp("(^|\\.)"+d.join("\\.(?:.*\\.)?")+"(\\.|$)")}a.namespace=a.namespace||d.join(".");f=c.data(this,this.nodeType?"events":"__events__");if(typeof f==="function")f=f.events;d=(f||{})[a.type];if(f&&d){d=d.slice(0);f=0;for(var k=d.length;f<k;f++){var l=d[f];if(b||e.test(l.namespace)){a.handler=l.handler;a.data=
3931+l.data;a.handleObj=l;l=l.handler.apply(this,h);if(l!==A){a.result=l;if(l===false){a.preventDefault();a.stopPropagation()}}if(a.isImmediatePropagationStopped())break}}}return a.result},props:"altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),
3932+fix:function(a){if(a[c.expando])return a;var b=a;a=c.Event(b);for(var d=this.props.length,e;d;){e=this.props[--d];a[e]=b[e]}if(!a.target)a.target=a.srcElement||u;if(a.target.nodeType===3)a.target=a.target.parentNode;if(!a.relatedTarget&&a.fromElement)a.relatedTarget=a.fromElement===a.target?a.toElement:a.fromElement;if(a.pageX==null&&a.clientX!=null){b=u.documentElement;d=u.body;a.pageX=a.clientX+(b&&b.scrollLeft||d&&d.scrollLeft||0)-(b&&b.clientLeft||d&&d.clientLeft||0);a.pageY=a.clientY+(b&&b.scrollTop||
3933+d&&d.scrollTop||0)-(b&&b.clientTop||d&&d.clientTop||0)}if(a.which==null&&(a.charCode!=null||a.keyCode!=null))a.which=a.charCode!=null?a.charCode:a.keyCode;if(!a.metaKey&&a.ctrlKey)a.metaKey=a.ctrlKey;if(!a.which&&a.button!==A)a.which=a.button&1?1:a.button&2?3:a.button&4?2:0;return a},guid:1E8,proxy:c.proxy,special:{ready:{setup:c.bindReady,teardown:c.noop},live:{add:function(a){c.event.add(this,Y(a.origType,a.selector),c.extend({},a,{handler:Ga,guid:a.handler.guid}))},remove:function(a){c.event.remove(this,
3934+Y(a.origType,a.selector),a)}},beforeunload:{setup:function(a,b,d){if(c.isWindow(this))this.onbeforeunload=d},teardown:function(a,b){if(this.onbeforeunload===b)this.onbeforeunload=null}}}};c.removeEvent=u.removeEventListener?function(a,b,d){a.removeEventListener&&a.removeEventListener(b,d,false)}:function(a,b,d){a.detachEvent&&a.detachEvent("on"+b,d)};c.Event=function(a){if(!this.preventDefault)return new c.Event(a);if(a&&a.type){this.originalEvent=a;this.type=a.type}else this.type=a;this.timeStamp=
3935+c.now();this[c.expando]=true};c.Event.prototype={preventDefault:function(){this.isDefaultPrevented=ba;var a=this.originalEvent;if(a)if(a.preventDefault)a.preventDefault();else a.returnValue=false},stopPropagation:function(){this.isPropagationStopped=ba;var a=this.originalEvent;if(a){a.stopPropagation&&a.stopPropagation();a.cancelBubble=true}},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=ba;this.stopPropagation()},isDefaultPrevented:U,isPropagationStopped:U,isImmediatePropagationStopped:U};
3936+var ta=function(a){var b=a.relatedTarget;try{for(;b&&b!==this;)b=b.parentNode;if(b!==this){a.type=a.data;c.event.handle.apply(this,arguments)}}catch(d){}},ua=function(a){a.type=a.data;c.event.handle.apply(this,arguments)};c.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(a,b){c.event.special[a]={setup:function(d){c.event.add(this,b,d&&d.selector?ua:ta,a)},teardown:function(d){c.event.remove(this,b,d&&d.selector?ua:ta)}}});if(!c.support.submitBubbles)c.event.special.submit={setup:function(){if(this.nodeName.toLowerCase()!==
3937+"form"){c.event.add(this,"click.specialSubmit",function(a){var b=a.target,d=b.type;if((d==="submit"||d==="image")&&c(b).closest("form").length){a.liveFired=A;return ja("submit",this,arguments)}});c.event.add(this,"keypress.specialSubmit",function(a){var b=a.target,d=b.type;if((d==="text"||d==="password")&&c(b).closest("form").length&&a.keyCode===13){a.liveFired=A;return ja("submit",this,arguments)}})}else return false},teardown:function(){c.event.remove(this,".specialSubmit")}};if(!c.support.changeBubbles){var V,
3938+va=function(a){var b=a.type,d=a.value;if(b==="radio"||b==="checkbox")d=a.checked;else if(b==="select-multiple")d=a.selectedIndex>-1?c.map(a.options,function(e){return e.selected}).join("-"):"";else if(a.nodeName.toLowerCase()==="select")d=a.selectedIndex;return d},Z=function(a,b){var d=a.target,e,f;if(!(!ha.test(d.nodeName)||d.readOnly)){e=c.data(d,"_change_data");f=va(d);if(a.type!=="focusout"||d.type!=="radio")c.data(d,"_change_data",f);if(!(e===A||f===e))if(e!=null||f){a.type="change";a.liveFired=
3939+A;return c.event.trigger(a,b,d)}}};c.event.special.change={filters:{focusout:Z,beforedeactivate:Z,click:function(a){var b=a.target,d=b.type;if(d==="radio"||d==="checkbox"||b.nodeName.toLowerCase()==="select")return Z.call(this,a)},keydown:function(a){var b=a.target,d=b.type;if(a.keyCode===13&&b.nodeName.toLowerCase()!=="textarea"||a.keyCode===32&&(d==="checkbox"||d==="radio")||d==="select-multiple")return Z.call(this,a)},beforeactivate:function(a){a=a.target;c.data(a,"_change_data",va(a))}},setup:function(){if(this.type===
3940+"file")return false;for(var a in V)c.event.add(this,a+".specialChange",V[a]);return ha.test(this.nodeName)},teardown:function(){c.event.remove(this,".specialChange");return ha.test(this.nodeName)}};V=c.event.special.change.filters;V.focus=V.beforeactivate}u.addEventListener&&c.each({focus:"focusin",blur:"focusout"},function(a,b){function d(e){e=c.event.fix(e);e.type=b;return c.event.trigger(e,null,e.target)}c.event.special[b]={setup:function(){sa[b]++===0&&u.addEventListener(a,d,true)},teardown:function(){--sa[b]===
3941+0&&u.removeEventListener(a,d,true)}}});c.each(["bind","one"],function(a,b){c.fn[b]=function(d,e,f){if(typeof d==="object"){for(var h in d)this[b](h,e,d[h],f);return this}if(c.isFunction(e)||e===false){f=e;e=A}var k=b==="one"?c.proxy(f,function(n){c(this).unbind(n,k);return f.apply(this,arguments)}):f;if(d==="unload"&&b!=="one")this.one(d,e,f);else{h=0;for(var l=this.length;h<l;h++)c.event.add(this[h],d,k,e)}return this}});c.fn.extend({unbind:function(a,b){if(typeof a==="object"&&!a.preventDefault)for(var d in a)this.unbind(d,
3942+a[d]);else{d=0;for(var e=this.length;d<e;d++)c.event.remove(this[d],a,b)}return this},delegate:function(a,b,d,e){return this.live(b,d,e,a)},undelegate:function(a,b,d){return arguments.length===0?this.unbind("live"):this.die(b,null,d,a)},trigger:function(a,b){return this.each(function(){c.event.trigger(a,b,this)})},triggerHandler:function(a,b){if(this[0]){var d=c.Event(a);d.preventDefault();d.stopPropagation();c.event.trigger(d,b,this[0]);return d.result}},toggle:function(a){for(var b=arguments,d=
3943+1;d<b.length;)c.proxy(a,b[d++]);return this.click(c.proxy(a,function(e){var f=(c.data(this,"lastToggle"+a.guid)||0)%d;c.data(this,"lastToggle"+a.guid,f+1);e.preventDefault();return b[f].apply(this,arguments)||false}))},hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}});var wa={focus:"focusin",blur:"focusout",mouseenter:"mouseover",mouseleave:"mouseout"};c.each(["live","die"],function(a,b){c.fn[b]=function(d,e,f,h){var k,l=0,n,s,v=h||this.selector;h=h?this:c(this.context);if(typeof d===
3944+"object"&&!d.preventDefault){for(k in d)h[b](k,e,d[k],v);return this}if(c.isFunction(e)){f=e;e=A}for(d=(d||"").split(" ");(k=d[l++])!=null;){n=X.exec(k);s="";if(n){s=n[0];k=k.replace(X,"")}if(k==="hover")d.push("mouseenter"+s,"mouseleave"+s);else{n=k;if(k==="focus"||k==="blur"){d.push(wa[k]+s);k+=s}else k=(wa[k]||k)+s;if(b==="live"){s=0;for(var B=h.length;s<B;s++)c.event.add(h[s],"live."+Y(k,v),{data:e,selector:v,handler:f,origType:k,origHandler:f,preType:n})}else h.unbind("live."+Y(k,v),f)}}return this}});
3945+c.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error".split(" "),function(a,b){c.fn[b]=function(d,e){if(e==null){e=d;d=null}return arguments.length>0?this.bind(b,d,e):this.trigger(b)};if(c.attrFn)c.attrFn[b]=true});E.attachEvent&&!E.addEventListener&&c(E).bind("unload",function(){for(var a in c.cache)if(c.cache[a].handle)try{c.event.remove(c.cache[a].handle.elem)}catch(b){}});
3946+(function(){function a(g,j,o,m,p,q){p=0;for(var t=m.length;p<t;p++){var x=m[p];if(x){x=x[g];for(var C=false;x;){if(x.sizcache===o){C=m[x.sizset];break}if(x.nodeType===1&&!q){x.sizcache=o;x.sizset=p}if(x.nodeName.toLowerCase()===j){C=x;break}x=x[g]}m[p]=C}}}function b(g,j,o,m,p,q){p=0;for(var t=m.length;p<t;p++){var x=m[p];if(x){x=x[g];for(var C=false;x;){if(x.sizcache===o){C=m[x.sizset];break}if(x.nodeType===1){if(!q){x.sizcache=o;x.sizset=p}if(typeof j!=="string"){if(x===j){C=true;break}}else if(l.filter(j,
3947+[x]).length>0){C=x;break}}x=x[g]}m[p]=C}}}var d=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,e=0,f=Object.prototype.toString,h=false,k=true;[0,0].sort(function(){k=false;return 0});var l=function(g,j,o,m){o=o||[];var p=j=j||u;if(j.nodeType!==1&&j.nodeType!==9)return[];if(!g||typeof g!=="string")return o;var q=[],t,x,C,P,N=true,R=l.isXML(j),Q=g,L;do{d.exec("");if(t=d.exec(Q)){Q=t[3];q.push(t[1]);if(t[2]){P=t[3];
3948+break}}}while(t);if(q.length>1&&s.exec(g))if(q.length===2&&n.relative[q[0]])x=M(q[0]+q[1],j);else for(x=n.relative[q[0]]?[j]:l(q.shift(),j);q.length;){g=q.shift();if(n.relative[g])g+=q.shift();x=M(g,x)}else{if(!m&&q.length>1&&j.nodeType===9&&!R&&n.match.ID.test(q[0])&&!n.match.ID.test(q[q.length-1])){t=l.find(q.shift(),j,R);j=t.expr?l.filter(t.expr,t.set)[0]:t.set[0]}if(j){t=m?{expr:q.pop(),set:D(m)}:l.find(q.pop(),q.length===1&&(q[0]==="~"||q[0]==="+")&&j.parentNode?j.parentNode:j,R);x=t.expr?l.filter(t.expr,
3949+t.set):t.set;if(q.length>0)C=D(x);else N=false;for(;q.length;){t=L=q.pop();if(n.relative[L])t=q.pop();else L="";if(t==null)t=j;n.relative[L](C,t,R)}}else C=[]}C||(C=x);C||l.error(L||g);if(f.call(C)==="[object Array]")if(N)if(j&&j.nodeType===1)for(g=0;C[g]!=null;g++){if(C[g]&&(C[g]===true||C[g].nodeType===1&&l.contains(j,C[g])))o.push(x[g])}else for(g=0;C[g]!=null;g++)C[g]&&C[g].nodeType===1&&o.push(x[g]);else o.push.apply(o,C);else D(C,o);if(P){l(P,p,o,m);l.uniqueSort(o)}return o};l.uniqueSort=function(g){if(w){h=
3950+k;g.sort(w);if(h)for(var j=1;j<g.length;j++)g[j]===g[j-1]&&g.splice(j--,1)}return g};l.matches=function(g,j){return l(g,null,null,j)};l.matchesSelector=function(g,j){return l(j,null,null,[g]).length>0};l.find=function(g,j,o){var m;if(!g)return[];for(var p=0,q=n.order.length;p<q;p++){var t=n.order[p],x;if(x=n.leftMatch[t].exec(g)){var C=x[1];x.splice(1,1);if(C.substr(C.length-1)!=="\\"){x[1]=(x[1]||"").replace(/\\/g,"");m=n.find[t](x,j,o);if(m!=null){g=g.replace(n.match[t],"");break}}}}m||(m=j.getElementsByTagName("*"));
3951+return{set:m,expr:g}};l.filter=function(g,j,o,m){for(var p=g,q=[],t=j,x,C,P=j&&j[0]&&l.isXML(j[0]);g&&j.length;){for(var N in n.filter)if((x=n.leftMatch[N].exec(g))!=null&&x[2]){var R=n.filter[N],Q,L;L=x[1];C=false;x.splice(1,1);if(L.substr(L.length-1)!=="\\"){if(t===q)q=[];if(n.preFilter[N])if(x=n.preFilter[N](x,t,o,q,m,P)){if(x===true)continue}else C=Q=true;if(x)for(var i=0;(L=t[i])!=null;i++)if(L){Q=R(L,x,i,t);var r=m^!!Q;if(o&&Q!=null)if(r)C=true;else t[i]=false;else if(r){q.push(L);C=true}}if(Q!==
3952+A){o||(t=q);g=g.replace(n.match[N],"");if(!C)return[];break}}}if(g===p)if(C==null)l.error(g);else break;p=g}return t};l.error=function(g){throw"Syntax error, unrecognized expression: "+g;};var n=l.selectors={order:["ID","NAME","TAG"],match:{ID:/#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,CLASS:/\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,NAME:/\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,ATTR:/\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/,TAG:/^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,CHILD:/:(only|nth|last|first)-child(?:\((even|odd|[\dn+\-]*)\))?/,
3953+POS:/:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,PSEUDO:/:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/},leftMatch:{},attrMap:{"class":"className","for":"htmlFor"},attrHandle:{href:function(g){return g.getAttribute("href")}},relative:{"+":function(g,j){var o=typeof j==="string",m=o&&!/\W/.test(j);o=o&&!m;if(m)j=j.toLowerCase();m=0;for(var p=g.length,q;m<p;m++)if(q=g[m]){for(;(q=q.previousSibling)&&q.nodeType!==1;);g[m]=o||q&&q.nodeName.toLowerCase()===
3954+j?q||false:q===j}o&&l.filter(j,g,true)},">":function(g,j){var o=typeof j==="string",m,p=0,q=g.length;if(o&&!/\W/.test(j))for(j=j.toLowerCase();p<q;p++){if(m=g[p]){o=m.parentNode;g[p]=o.nodeName.toLowerCase()===j?o:false}}else{for(;p<q;p++)if(m=g[p])g[p]=o?m.parentNode:m.parentNode===j;o&&l.filter(j,g,true)}},"":function(g,j,o){var m=e++,p=b,q;if(typeof j==="string"&&!/\W/.test(j)){q=j=j.toLowerCase();p=a}p("parentNode",j,m,g,q,o)},"~":function(g,j,o){var m=e++,p=b,q;if(typeof j==="string"&&!/\W/.test(j)){q=
3955+j=j.toLowerCase();p=a}p("previousSibling",j,m,g,q,o)}},find:{ID:function(g,j,o){if(typeof j.getElementById!=="undefined"&&!o)return(g=j.getElementById(g[1]))&&g.parentNode?[g]:[]},NAME:function(g,j){if(typeof j.getElementsByName!=="undefined"){for(var o=[],m=j.getElementsByName(g[1]),p=0,q=m.length;p<q;p++)m[p].getAttribute("name")===g[1]&&o.push(m[p]);return o.length===0?null:o}},TAG:function(g,j){return j.getElementsByTagName(g[1])}},preFilter:{CLASS:function(g,j,o,m,p,q){g=" "+g[1].replace(/\\/g,
3956+"")+" ";if(q)return g;q=0;for(var t;(t=j[q])!=null;q++)if(t)if(p^(t.className&&(" "+t.className+" ").replace(/[\t\n]/g," ").indexOf(g)>=0))o||m.push(t);else if(o)j[q]=false;return false},ID:function(g){return g[1].replace(/\\/g,"")},TAG:function(g){return g[1].toLowerCase()},CHILD:function(g){if(g[1]==="nth"){var j=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(g[2]==="even"&&"2n"||g[2]==="odd"&&"2n+1"||!/\D/.test(g[2])&&"0n+"+g[2]||g[2]);g[2]=j[1]+(j[2]||1)-0;g[3]=j[3]-0}g[0]=e++;return g},ATTR:function(g,j,o,
3957+m,p,q){j=g[1].replace(/\\/g,"");if(!q&&n.attrMap[j])g[1]=n.attrMap[j];if(g[2]==="~=")g[4]=" "+g[4]+" ";return g},PSEUDO:function(g,j,o,m,p){if(g[1]==="not")if((d.exec(g[3])||"").length>1||/^\w/.test(g[3]))g[3]=l(g[3],null,null,j);else{g=l.filter(g[3],j,o,true^p);o||m.push.apply(m,g);return false}else if(n.match.POS.test(g[0])||n.match.CHILD.test(g[0]))return true;return g},POS:function(g){g.unshift(true);return g}},filters:{enabled:function(g){return g.disabled===false&&g.type!=="hidden"},disabled:function(g){return g.disabled===
3958+true},checked:function(g){return g.checked===true},selected:function(g){return g.selected===true},parent:function(g){return!!g.firstChild},empty:function(g){return!g.firstChild},has:function(g,j,o){return!!l(o[3],g).length},header:function(g){return/h\d/i.test(g.nodeName)},text:function(g){return"text"===g.type},radio:function(g){return"radio"===g.type},checkbox:function(g){return"checkbox"===g.type},file:function(g){return"file"===g.type},password:function(g){return"password"===g.type},submit:function(g){return"submit"===
3959+g.type},image:function(g){return"image"===g.type},reset:function(g){return"reset"===g.type},button:function(g){return"button"===g.type||g.nodeName.toLowerCase()==="button"},input:function(g){return/input|select|textarea|button/i.test(g.nodeName)}},setFilters:{first:function(g,j){return j===0},last:function(g,j,o,m){return j===m.length-1},even:function(g,j){return j%2===0},odd:function(g,j){return j%2===1},lt:function(g,j,o){return j<o[3]-0},gt:function(g,j,o){return j>o[3]-0},nth:function(g,j,o){return o[3]-
3960+0===j},eq:function(g,j,o){return o[3]-0===j}},filter:{PSEUDO:function(g,j,o,m){var p=j[1],q=n.filters[p];if(q)return q(g,o,j,m);else if(p==="contains")return(g.textContent||g.innerText||l.getText([g])||"").indexOf(j[3])>=0;else if(p==="not"){j=j[3];o=0;for(m=j.length;o<m;o++)if(j[o]===g)return false;return true}else l.error("Syntax error, unrecognized expression: "+p)},CHILD:function(g,j){var o=j[1],m=g;switch(o){case "only":case "first":for(;m=m.previousSibling;)if(m.nodeType===1)return false;if(o===
3961+"first")return true;m=g;case "last":for(;m=m.nextSibling;)if(m.nodeType===1)return false;return true;case "nth":o=j[2];var p=j[3];if(o===1&&p===0)return true;var q=j[0],t=g.parentNode;if(t&&(t.sizcache!==q||!g.nodeIndex)){var x=0;for(m=t.firstChild;m;m=m.nextSibling)if(m.nodeType===1)m.nodeIndex=++x;t.sizcache=q}m=g.nodeIndex-p;return o===0?m===0:m%o===0&&m/o>=0}},ID:function(g,j){return g.nodeType===1&&g.getAttribute("id")===j},TAG:function(g,j){return j==="*"&&g.nodeType===1||g.nodeName.toLowerCase()===
3962+j},CLASS:function(g,j){return(" "+(g.className||g.getAttribute("class"))+" ").indexOf(j)>-1},ATTR:function(g,j){var o=j[1];o=n.attrHandle[o]?n.attrHandle[o](g):g[o]!=null?g[o]:g.getAttribute(o);var m=o+"",p=j[2],q=j[4];return o==null?p==="!=":p==="="?m===q:p==="*="?m.indexOf(q)>=0:p==="~="?(" "+m+" ").indexOf(q)>=0:!q?m&&o!==false:p==="!="?m!==q:p==="^="?m.indexOf(q)===0:p==="$="?m.substr(m.length-q.length)===q:p==="|="?m===q||m.substr(0,q.length+1)===q+"-":false},POS:function(g,j,o,m){var p=n.setFilters[j[2]];
3963+if(p)return p(g,o,j,m)}}},s=n.match.POS,v=function(g,j){return"\\"+(j-0+1)},B;for(B in n.match){n.match[B]=RegExp(n.match[B].source+/(?![^\[]*\])(?![^\(]*\))/.source);n.leftMatch[B]=RegExp(/(^(?:.|\r|\n)*?)/.source+n.match[B].source.replace(/\\(\d+)/g,v))}var D=function(g,j){g=Array.prototype.slice.call(g,0);if(j){j.push.apply(j,g);return j}return g};try{Array.prototype.slice.call(u.documentElement.childNodes,0)}catch(H){D=function(g,j){var o=j||[],m=0;if(f.call(g)==="[object Array]")Array.prototype.push.apply(o,
3964+g);else if(typeof g.length==="number")for(var p=g.length;m<p;m++)o.push(g[m]);else for(;g[m];m++)o.push(g[m]);return o}}var w,G;if(u.documentElement.compareDocumentPosition)w=function(g,j){if(g===j){h=true;return 0}if(!g.compareDocumentPosition||!j.compareDocumentPosition)return g.compareDocumentPosition?-1:1;return g.compareDocumentPosition(j)&4?-1:1};else{w=function(g,j){var o=[],m=[],p=g.parentNode,q=j.parentNode,t=p;if(g===j){h=true;return 0}else if(p===q)return G(g,j);else if(p){if(!q)return 1}else return-1;
3965+for(;t;){o.unshift(t);t=t.parentNode}for(t=q;t;){m.unshift(t);t=t.parentNode}p=o.length;q=m.length;for(t=0;t<p&&t<q;t++)if(o[t]!==m[t])return G(o[t],m[t]);return t===p?G(g,m[t],-1):G(o[t],j,1)};G=function(g,j,o){if(g===j)return o;for(g=g.nextSibling;g;){if(g===j)return-1;g=g.nextSibling}return 1}}l.getText=function(g){for(var j="",o,m=0;g[m];m++){o=g[m];if(o.nodeType===3||o.nodeType===4)j+=o.nodeValue;else if(o.nodeType!==8)j+=l.getText(o.childNodes)}return j};(function(){var g=u.createElement("div"),
3966+j="script"+(new Date).getTime();g.innerHTML="<a name='"+j+"'/>";var o=u.documentElement;o.insertBefore(g,o.firstChild);if(u.getElementById(j)){n.find.ID=function(m,p,q){if(typeof p.getElementById!=="undefined"&&!q)return(p=p.getElementById(m[1]))?p.id===m[1]||typeof p.getAttributeNode!=="undefined"&&p.getAttributeNode("id").nodeValue===m[1]?[p]:A:[]};n.filter.ID=function(m,p){var q=typeof m.getAttributeNode!=="undefined"&&m.getAttributeNode("id");return m.nodeType===1&&q&&q.nodeValue===p}}o.removeChild(g);
3967+o=g=null})();(function(){var g=u.createElement("div");g.appendChild(u.createComment(""));if(g.getElementsByTagName("*").length>0)n.find.TAG=function(j,o){var m=o.getElementsByTagName(j[1]);if(j[1]==="*"){for(var p=[],q=0;m[q];q++)m[q].nodeType===1&&p.push(m[q]);m=p}return m};g.innerHTML="<a href='#'></a>";if(g.firstChild&&typeof g.firstChild.getAttribute!=="undefined"&&g.firstChild.getAttribute("href")!=="#")n.attrHandle.href=function(j){return j.getAttribute("href",2)};g=null})();u.querySelectorAll&&
3968+function(){var g=l,j=u.createElement("div");j.innerHTML="<p class='TEST'></p>";if(!(j.querySelectorAll&&j.querySelectorAll(".TEST").length===0)){l=function(m,p,q,t){p=p||u;if(!t&&!l.isXML(p))if(p.nodeType===9)try{return D(p.querySelectorAll(m),q)}catch(x){}else if(p.nodeType===1&&p.nodeName.toLowerCase()!=="object"){var C=p.id,P=p.id="__sizzle__";try{return D(p.querySelectorAll("#"+P+" "+m),q)}catch(N){}finally{if(C)p.id=C;else p.removeAttribute("id")}}return g(m,p,q,t)};for(var o in g)l[o]=g[o];
3969+j=null}}();(function(){var g=u.documentElement,j=g.matchesSelector||g.mozMatchesSelector||g.webkitMatchesSelector||g.msMatchesSelector,o=false;try{j.call(u.documentElement,":sizzle")}catch(m){o=true}if(j)l.matchesSelector=function(p,q){try{if(o||!n.match.PSEUDO.test(q))return j.call(p,q)}catch(t){}return l(q,null,null,[p]).length>0}})();(function(){var g=u.createElement("div");g.innerHTML="<div class='test e'></div><div class='test'></div>";if(!(!g.getElementsByClassName||g.getElementsByClassName("e").length===
3970+0)){g.lastChild.className="e";if(g.getElementsByClassName("e").length!==1){n.order.splice(1,0,"CLASS");n.find.CLASS=function(j,o,m){if(typeof o.getElementsByClassName!=="undefined"&&!m)return o.getElementsByClassName(j[1])};g=null}}})();l.contains=u.documentElement.contains?function(g,j){return g!==j&&(g.contains?g.contains(j):true)}:function(g,j){return!!(g.compareDocumentPosition(j)&16)};l.isXML=function(g){return(g=(g?g.ownerDocument||g:0).documentElement)?g.nodeName!=="HTML":false};var M=function(g,
3971+j){for(var o=[],m="",p,q=j.nodeType?[j]:j;p=n.match.PSEUDO.exec(g);){m+=p[0];g=g.replace(n.match.PSEUDO,"")}g=n.relative[g]?g+"*":g;p=0;for(var t=q.length;p<t;p++)l(g,q[p],o);return l.filter(m,o)};c.find=l;c.expr=l.selectors;c.expr[":"]=c.expr.filters;c.unique=l.uniqueSort;c.text=l.getText;c.isXMLDoc=l.isXML;c.contains=l.contains})();var Wa=/Until$/,Xa=/^(?:parents|prevUntil|prevAll)/,Ya=/,/,Ja=/^.[^:#\[\.,]*$/,Za=Array.prototype.slice,$a=c.expr.match.POS;c.fn.extend({find:function(a){for(var b=this.pushStack("",
3972+"find",a),d=0,e=0,f=this.length;e<f;e++){d=b.length;c.find(a,this[e],b);if(e>0)for(var h=d;h<b.length;h++)for(var k=0;k<d;k++)if(b[k]===b[h]){b.splice(h--,1);break}}return b},has:function(a){var b=c(a);return this.filter(function(){for(var d=0,e=b.length;d<e;d++)if(c.contains(this,b[d]))return true})},not:function(a){return this.pushStack(ka(this,a,false),"not",a)},filter:function(a){return this.pushStack(ka(this,a,true),"filter",a)},is:function(a){return!!a&&c.filter(a,this).length>0},closest:function(a,
3973+b){var d=[],e,f,h=this[0];if(c.isArray(a)){var k={},l,n=1;if(h&&a.length){e=0;for(f=a.length;e<f;e++){l=a[e];k[l]||(k[l]=c.expr.match.POS.test(l)?c(l,b||this.context):l)}for(;h&&h.ownerDocument&&h!==b;){for(l in k){e=k[l];if(e.jquery?e.index(h)>-1:c(h).is(e))d.push({selector:l,elem:h,level:n})}h=h.parentNode;n++}}return d}k=$a.test(a)?c(a,b||this.context):null;e=0;for(f=this.length;e<f;e++)for(h=this[e];h;)if(k?k.index(h)>-1:c.find.matchesSelector(h,a)){d.push(h);break}else{h=h.parentNode;if(!h||
3974+!h.ownerDocument||h===b)break}d=d.length>1?c.unique(d):d;return this.pushStack(d,"closest",a)},index:function(a){if(!a||typeof a==="string")return c.inArray(this[0],a?c(a):this.parent().children());return c.inArray(a.jquery?a[0]:a,this)},add:function(a,b){var d=typeof a==="string"?c(a,b||this.context):c.makeArray(a),e=c.merge(this.get(),d);return this.pushStack(!d[0]||!d[0].parentNode||d[0].parentNode.nodeType===11||!e[0]||!e[0].parentNode||e[0].parentNode.nodeType===11?e:c.unique(e))},andSelf:function(){return this.add(this.prevObject)}});
3975+c.each({parent:function(a){return(a=a.parentNode)&&a.nodeType!==11?a:null},parents:function(a){return c.dir(a,"parentNode")},parentsUntil:function(a,b,d){return c.dir(a,"parentNode",d)},next:function(a){return c.nth(a,2,"nextSibling")},prev:function(a){return c.nth(a,2,"previousSibling")},nextAll:function(a){return c.dir(a,"nextSibling")},prevAll:function(a){return c.dir(a,"previousSibling")},nextUntil:function(a,b,d){return c.dir(a,"nextSibling",d)},prevUntil:function(a,b,d){return c.dir(a,"previousSibling",
3976+d)},siblings:function(a){return c.sibling(a.parentNode.firstChild,a)},children:function(a){return c.sibling(a.firstChild)},contents:function(a){return c.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:c.makeArray(a.childNodes)}},function(a,b){c.fn[a]=function(d,e){var f=c.map(this,b,d);Wa.test(a)||(e=d);if(e&&typeof e==="string")f=c.filter(e,f);f=this.length>1?c.unique(f):f;if((this.length>1||Ya.test(e))&&Xa.test(a))f=f.reverse();return this.pushStack(f,a,Za.call(arguments).join(","))}});
3977+c.extend({filter:function(a,b,d){if(d)a=":not("+a+")";return b.length===1?c.find.matchesSelector(b[0],a)?[b[0]]:[]:c.find.matches(a,b)},dir:function(a,b,d){var e=[];for(a=a[b];a&&a.nodeType!==9&&(d===A||a.nodeType!==1||!c(a).is(d));){a.nodeType===1&&e.push(a);a=a[b]}return e},nth:function(a,b,d){b=b||1;for(var e=0;a;a=a[d])if(a.nodeType===1&&++e===b)break;return a},sibling:function(a,b){for(var d=[];a;a=a.nextSibling)a.nodeType===1&&a!==b&&d.push(a);return d}});var xa=/ jQuery\d+="(?:\d+|null)"/g,
3978+$=/^\s+/,ya=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,za=/<([\w:]+)/,ab=/<tbody/i,bb=/<|&#?\w+;/,Aa=/<(?:script|object|embed|option|style)/i,Ba=/checked\s*(?:[^=]|=\s*.checked.)/i,cb=/\=([^="'>\s]+\/)>/g,O={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],
3979+area:[1,"<map>","</map>"],_default:[0,"",""]};O.optgroup=O.option;O.tbody=O.tfoot=O.colgroup=O.caption=O.thead;O.th=O.td;if(!c.support.htmlSerialize)O._default=[1,"div<div>","</div>"];c.fn.extend({text:function(a){if(c.isFunction(a))return this.each(function(b){var d=c(this);d.text(a.call(this,b,d.text()))});if(typeof a!=="object"&&a!==A)return this.empty().append((this[0]&&this[0].ownerDocument||u).createTextNode(a));return c.text(this)},wrapAll:function(a){if(c.isFunction(a))return this.each(function(d){c(this).wrapAll(a.call(this,
3980+d))});if(this[0]){var b=c(a,this[0].ownerDocument).eq(0).clone(true);this[0].parentNode&&b.insertBefore(this[0]);b.map(function(){for(var d=this;d.firstChild&&d.firstChild.nodeType===1;)d=d.firstChild;return d}).append(this)}return this},wrapInner:function(a){if(c.isFunction(a))return this.each(function(b){c(this).wrapInner(a.call(this,b))});return this.each(function(){var b=c(this),d=b.contents();d.length?d.wrapAll(a):b.append(a)})},wrap:function(a){return this.each(function(){c(this).wrapAll(a)})},
3981+unwrap:function(){return this.parent().each(function(){c.nodeName(this,"body")||c(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b,this)});else if(arguments.length){var a=
3982+c(arguments[0]);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b,this.nextSibling)});else if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,c(arguments[0]).toArray());return a}},remove:function(a,b){for(var d=0,e;(e=this[d])!=null;d++)if(!a||c.filter(a,[e]).length){if(!b&&e.nodeType===1){c.cleanData(e.getElementsByTagName("*"));
3983+c.cleanData([e])}e.parentNode&&e.parentNode.removeChild(e)}return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++)for(b.nodeType===1&&c.cleanData(b.getElementsByTagName("*"));b.firstChild;)b.removeChild(b.firstChild);return this},clone:function(a){var b=this.map(function(){if(!c.support.noCloneEvent&&!c.isXMLDoc(this)){var d=this.outerHTML,e=this.ownerDocument;if(!d){d=e.createElement("div");d.appendChild(this.cloneNode(true));d=d.innerHTML}return c.clean([d.replace(xa,"").replace(cb,'="$1">').replace($,
3984+"")],e)[0]}else return this.cloneNode(true)});if(a===true){la(this,b);la(this.find("*"),b.find("*"))}return b},html:function(a){if(a===A)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(xa,""):null;else if(typeof a==="string"&&!Aa.test(a)&&(c.support.leadingWhitespace||!$.test(a))&&!O[(za.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(ya,"<$1></$2>");try{for(var b=0,d=this.length;b<d;b++)if(this[b].nodeType===1){c.cleanData(this[b].getElementsByTagName("*"));this[b].innerHTML=a}}catch(e){this.empty().append(a)}}else c.isFunction(a)?
3985+this.each(function(f){var h=c(this);h.html(a.call(this,f,h.html()))}):this.empty().append(a);return this},replaceWith:function(a){if(this[0]&&this[0].parentNode){if(c.isFunction(a))return this.each(function(b){var d=c(this),e=d.html();d.replaceWith(a.call(this,b,e))});if(typeof a!=="string")a=c(a).detach();return this.each(function(){var b=this.nextSibling,d=this.parentNode;c(this).remove();b?c(b).before(a):c(d).append(a)})}else return this.pushStack(c(c.isFunction(a)?a():a),"replaceWith",a)},detach:function(a){return this.remove(a,
3986+true)},domManip:function(a,b,d){var e,f,h=a[0],k=[],l;if(!c.support.checkClone&&arguments.length===3&&typeof h==="string"&&Ba.test(h))return this.each(function(){c(this).domManip(a,b,d,true)});if(c.isFunction(h))return this.each(function(s){var v=c(this);a[0]=h.call(this,s,b?v.html():A);v.domManip(a,b,d)});if(this[0]){e=h&&h.parentNode;e=c.support.parentNode&&e&&e.nodeType===11&&e.childNodes.length===this.length?{fragment:e}:c.buildFragment(a,this,k);l=e.fragment;if(f=l.childNodes.length===1?l=l.firstChild:
3987+l.firstChild){b=b&&c.nodeName(f,"tr");f=0;for(var n=this.length;f<n;f++)d.call(b?c.nodeName(this[f],"table")?this[f].getElementsByTagName("tbody")[0]||this[f].appendChild(this[f].ownerDocument.createElement("tbody")):this[f]:this[f],f>0||e.cacheable||this.length>1?l.cloneNode(true):l)}k.length&&c.each(k,Ka)}return this}});c.buildFragment=function(a,b,d){var e,f,h;b=b&&b[0]?b[0].ownerDocument||b[0]:u;if(a.length===1&&typeof a[0]==="string"&&a[0].length<512&&b===u&&!Aa.test(a[0])&&(c.support.checkClone||
3988+!Ba.test(a[0]))){f=true;if(h=c.fragments[a[0]])if(h!==1)e=h}if(!e){e=b.createDocumentFragment();c.clean(a,b,e,d)}if(f)c.fragments[a[0]]=h?e:1;return{fragment:e,cacheable:f}};c.fragments={};c.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){c.fn[a]=function(d){var e=[];d=c(d);var f=this.length===1&&this[0].parentNode;if(f&&f.nodeType===11&&f.childNodes.length===1&&d.length===1){d[b](this[0]);return this}else{f=0;for(var h=
3989+d.length;f<h;f++){var k=(f>0?this.clone(true):this).get();c(d[f])[b](k);e=e.concat(k)}return this.pushStack(e,a,d.selector)}}});c.extend({clean:function(a,b,d,e){b=b||u;if(typeof b.createElement==="undefined")b=b.ownerDocument||b[0]&&b[0].ownerDocument||u;for(var f=[],h=0,k;(k=a[h])!=null;h++){if(typeof k==="number")k+="";if(k){if(typeof k==="string"&&!bb.test(k))k=b.createTextNode(k);else if(typeof k==="string"){k=k.replace(ya,"<$1></$2>");var l=(za.exec(k)||["",""])[1].toLowerCase(),n=O[l]||O._default,
3990+s=n[0],v=b.createElement("div");for(v.innerHTML=n[1]+k+n[2];s--;)v=v.lastChild;if(!c.support.tbody){s=ab.test(k);l=l==="table"&&!s?v.firstChild&&v.firstChild.childNodes:n[1]==="<table>"&&!s?v.childNodes:[];for(n=l.length-1;n>=0;--n)c.nodeName(l[n],"tbody")&&!l[n].childNodes.length&&l[n].parentNode.removeChild(l[n])}!c.support.leadingWhitespace&&$.test(k)&&v.insertBefore(b.createTextNode($.exec(k)[0]),v.firstChild);k=v.childNodes}if(k.nodeType)f.push(k);else f=c.merge(f,k)}}if(d)for(h=0;f[h];h++)if(e&&
3991+c.nodeName(f[h],"script")&&(!f[h].type||f[h].type.toLowerCase()==="text/javascript"))e.push(f[h].parentNode?f[h].parentNode.removeChild(f[h]):f[h]);else{f[h].nodeType===1&&f.splice.apply(f,[h+1,0].concat(c.makeArray(f[h].getElementsByTagName("script"))));d.appendChild(f[h])}return f},cleanData:function(a){for(var b,d,e=c.cache,f=c.event.special,h=c.support.deleteExpando,k=0,l;(l=a[k])!=null;k++)if(!(l.nodeName&&c.noData[l.nodeName.toLowerCase()]))if(d=l[c.expando]){if((b=e[d])&&b.events)for(var n in b.events)f[n]?
3992+c.event.remove(l,n):c.removeEvent(l,n,b.handle);if(h)delete l[c.expando];else l.removeAttribute&&l.removeAttribute(c.expando);delete e[d]}}});var Ca=/alpha\([^)]*\)/i,db=/opacity=([^)]*)/,eb=/-([a-z])/ig,fb=/([A-Z])/g,Da=/^-?\d+(?:px)?$/i,gb=/^-?\d/,hb={position:"absolute",visibility:"hidden",display:"block"},La=["Left","Right"],Ma=["Top","Bottom"],W,ib=u.defaultView&&u.defaultView.getComputedStyle,jb=function(a,b){return b.toUpperCase()};c.fn.css=function(a,b){if(arguments.length===2&&b===A)return this;
3993+return c.access(this,a,b,true,function(d,e,f){return f!==A?c.style(d,e,f):c.css(d,e)})};c.extend({cssHooks:{opacity:{get:function(a,b){if(b){var d=W(a,"opacity","opacity");return d===""?"1":d}else return a.style.opacity}}},cssNumber:{zIndex:true,fontWeight:true,opacity:true,zoom:true,lineHeight:true},cssProps:{"float":c.support.cssFloat?"cssFloat":"styleFloat"},style:function(a,b,d,e){if(!(!a||a.nodeType===3||a.nodeType===8||!a.style)){var f,h=c.camelCase(b),k=a.style,l=c.cssHooks[h];b=c.cssProps[h]||
3994+h;if(d!==A){if(!(typeof d==="number"&&isNaN(d)||d==null)){if(typeof d==="number"&&!c.cssNumber[h])d+="px";if(!l||!("set"in l)||(d=l.set(a,d))!==A)try{k[b]=d}catch(n){}}}else{if(l&&"get"in l&&(f=l.get(a,false,e))!==A)return f;return k[b]}}},css:function(a,b,d){var e,f=c.camelCase(b),h=c.cssHooks[f];b=c.cssProps[f]||f;if(h&&"get"in h&&(e=h.get(a,true,d))!==A)return e;else if(W)return W(a,b,f)},swap:function(a,b,d){var e={},f;for(f in b){e[f]=a.style[f];a.style[f]=b[f]}d.call(a);for(f in b)a.style[f]=
3995+e[f]},camelCase:function(a){return a.replace(eb,jb)}});c.curCSS=c.css;c.each(["height","width"],function(a,b){c.cssHooks[b]={get:function(d,e,f){var h;if(e){if(d.offsetWidth!==0)h=ma(d,b,f);else c.swap(d,hb,function(){h=ma(d,b,f)});return h+"px"}},set:function(d,e){if(Da.test(e)){e=parseFloat(e);if(e>=0)return e+"px"}else return e}}});if(!c.support.opacity)c.cssHooks.opacity={get:function(a,b){return db.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?parseFloat(RegExp.$1)/100+"":
3996+b?"1":""},set:function(a,b){var d=a.style;d.zoom=1;var e=c.isNaN(b)?"":"alpha(opacity="+b*100+")",f=d.filter||"";d.filter=Ca.test(f)?f.replace(Ca,e):d.filter+" "+e}};if(ib)W=function(a,b,d){var e;d=d.replace(fb,"-$1").toLowerCase();if(!(b=a.ownerDocument.defaultView))return A;if(b=b.getComputedStyle(a,null)){e=b.getPropertyValue(d);if(e===""&&!c.contains(a.ownerDocument.documentElement,a))e=c.style(a,d)}return e};else if(u.documentElement.currentStyle)W=function(a,b){var d,e,f=a.currentStyle&&a.currentStyle[b],
3997+h=a.style;if(!Da.test(f)&&gb.test(f)){d=h.left;e=a.runtimeStyle.left;a.runtimeStyle.left=a.currentStyle.left;h.left=b==="fontSize"?"1em":f||0;f=h.pixelLeft+"px";h.left=d;a.runtimeStyle.left=e}return f};if(c.expr&&c.expr.filters){c.expr.filters.hidden=function(a){var b=a.offsetHeight;return a.offsetWidth===0&&b===0||!c.support.reliableHiddenOffsets&&(a.style.display||c.css(a,"display"))==="none"};c.expr.filters.visible=function(a){return!c.expr.filters.hidden(a)}}var kb=c.now(),lb=/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,
3998+mb=/^(?:select|textarea)/i,nb=/^(?:color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,ob=/^(?:GET|HEAD|DELETE)$/,Na=/\[\]$/,T=/\=\?(&|$)/,ia=/\?/,pb=/([?&])_=[^&]*/,qb=/^(\w+:)?\/\/([^\/?#]+)/,rb=/%20/g,sb=/#.*$/,Ea=c.fn.load;c.fn.extend({load:function(a,b,d){if(typeof a!=="string"&&Ea)return Ea.apply(this,arguments);else if(!this.length)return this;var e=a.indexOf(" ");if(e>=0){var f=a.slice(e,a.length);a=a.slice(0,e)}e="GET";if(b)if(c.isFunction(b)){d=
3999+b;b=null}else if(typeof b==="object"){b=c.param(b,c.ajaxSettings.traditional);e="POST"}var h=this;c.ajax({url:a,type:e,dataType:"html",data:b,complete:function(k,l){if(l==="success"||l==="notmodified")h.html(f?c("<div>").append(k.responseText.replace(lb,"")).find(f):k.responseText);d&&h.each(d,[k.responseText,l,k])}});return this},serialize:function(){return c.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?c.makeArray(this.elements):this}).filter(function(){return this.name&&
4000+!this.disabled&&(this.checked||mb.test(this.nodeName)||nb.test(this.type))}).map(function(a,b){var d=c(this).val();return d==null?null:c.isArray(d)?c.map(d,function(e){return{name:b.name,value:e}}):{name:b.name,value:d}}).get()}});c.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){c.fn[b]=function(d){return this.bind(b,d)}});c.extend({get:function(a,b,d,e){if(c.isFunction(b)){e=e||d;d=b;b=null}return c.ajax({type:"GET",url:a,data:b,success:d,dataType:e})},
4001+getScript:function(a,b){return c.get(a,null,b,"script")},getJSON:function(a,b,d){return c.get(a,b,d,"json")},post:function(a,b,d,e){if(c.isFunction(b)){e=e||d;d=b;b={}}return c.ajax({type:"POST",url:a,data:b,success:d,dataType:e})},ajaxSetup:function(a){c.extend(c.ajaxSettings,a)},ajaxSettings:{url:location.href,global:true,type:"GET",contentType:"application/x-www-form-urlencoded",processData:true,async:true,xhr:function(){return new E.XMLHttpRequest},accepts:{xml:"application/xml, text/xml",html:"text/html",
4002+script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},ajax:function(a){var b=c.extend(true,{},c.ajaxSettings,a),d,e,f,h=b.type.toUpperCase(),k=ob.test(h);b.url=b.url.replace(sb,"");b.context=a&&a.context!=null?a.context:b;if(b.data&&b.processData&&typeof b.data!=="string")b.data=c.param(b.data,b.traditional);if(b.dataType==="jsonp"){if(h==="GET")T.test(b.url)||(b.url+=(ia.test(b.url)?"&":"?")+(b.jsonp||"callback")+"=?");else if(!b.data||
4003+!T.test(b.data))b.data=(b.data?b.data+"&":"")+(b.jsonp||"callback")+"=?";b.dataType="json"}if(b.dataType==="json"&&(b.data&&T.test(b.data)||T.test(b.url))){d=b.jsonpCallback||"jsonp"+kb++;if(b.data)b.data=(b.data+"").replace(T,"="+d+"$1");b.url=b.url.replace(T,"="+d+"$1");b.dataType="script";var l=E[d];E[d]=function(m){f=m;c.handleSuccess(b,w,e,f);c.handleComplete(b,w,e,f);if(c.isFunction(l))l(m);else{E[d]=A;try{delete E[d]}catch(p){}}v&&v.removeChild(B)}}if(b.dataType==="script"&&b.cache===null)b.cache=
4004+false;if(b.cache===false&&h==="GET"){var n=c.now(),s=b.url.replace(pb,"$1_="+n);b.url=s+(s===b.url?(ia.test(b.url)?"&":"?")+"_="+n:"")}if(b.data&&h==="GET")b.url+=(ia.test(b.url)?"&":"?")+b.data;b.global&&c.active++===0&&c.event.trigger("ajaxStart");n=(n=qb.exec(b.url))&&(n[1]&&n[1]!==location.protocol||n[2]!==location.host);if(b.dataType==="script"&&h==="GET"&&n){var v=u.getElementsByTagName("head")[0]||u.documentElement,B=u.createElement("script");if(b.scriptCharset)B.charset=b.scriptCharset;B.src=
4005+b.url;if(!d){var D=false;B.onload=B.onreadystatechange=function(){if(!D&&(!this.readyState||this.readyState==="loaded"||this.readyState==="complete")){D=true;c.handleSuccess(b,w,e,f);c.handleComplete(b,w,e,f);B.onload=B.onreadystatechange=null;v&&B.parentNode&&v.removeChild(B)}}}v.insertBefore(B,v.firstChild);return A}var H=false,w=b.xhr();if(w){b.username?w.open(h,b.url,b.async,b.username,b.password):w.open(h,b.url,b.async);try{if(b.data!=null&&!k||a&&a.contentType)w.setRequestHeader("Content-Type",
4006+b.contentType);if(b.ifModified){c.lastModified[b.url]&&w.setRequestHeader("If-Modified-Since",c.lastModified[b.url]);c.etag[b.url]&&w.setRequestHeader("If-None-Match",c.etag[b.url])}n||w.setRequestHeader("X-Requested-With","XMLHttpRequest");w.setRequestHeader("Accept",b.dataType&&b.accepts[b.dataType]?b.accepts[b.dataType]+", */*; q=0.01":b.accepts._default)}catch(G){}if(b.beforeSend&&b.beforeSend.call(b.context,w,b)===false){b.global&&c.active--===1&&c.event.trigger("ajaxStop");w.abort();return false}b.global&&
4007+c.triggerGlobal(b,"ajaxSend",[w,b]);var M=w.onreadystatechange=function(m){if(!w||w.readyState===0||m==="abort"){H||c.handleComplete(b,w,e,f);H=true;if(w)w.onreadystatechange=c.noop}else if(!H&&w&&(w.readyState===4||m==="timeout")){H=true;w.onreadystatechange=c.noop;e=m==="timeout"?"timeout":!c.httpSuccess(w)?"error":b.ifModified&&c.httpNotModified(w,b.url)?"notmodified":"success";var p;if(e==="success")try{f=c.httpData(w,b.dataType,b)}catch(q){e="parsererror";p=q}if(e==="success"||e==="notmodified")d||
4008+c.handleSuccess(b,w,e,f);else c.handleError(b,w,e,p);d||c.handleComplete(b,w,e,f);m==="timeout"&&w.abort();if(b.async)w=null}};try{var g=w.abort;w.abort=function(){w&&g.call&&g.call(w);M("abort")}}catch(j){}b.async&&b.timeout>0&&setTimeout(function(){w&&!H&&M("timeout")},b.timeout);try{w.send(k||b.data==null?null:b.data)}catch(o){c.handleError(b,w,null,o);c.handleComplete(b,w,e,f)}b.async||M();return w}},param:function(a,b){var d=[],e=function(h,k){k=c.isFunction(k)?k():k;d[d.length]=encodeURIComponent(h)+
4009+"="+encodeURIComponent(k)};if(b===A)b=c.ajaxSettings.traditional;if(c.isArray(a)||a.jquery)c.each(a,function(){e(this.name,this.value)});else for(var f in a)ca(f,a[f],b,e);return d.join("&").replace(rb,"+")}});c.extend({active:0,lastModified:{},etag:{},handleError:function(a,b,d,e){a.error&&a.error.call(a.context,b,d,e);a.global&&c.triggerGlobal(a,"ajaxError",[b,a,e])},handleSuccess:function(a,b,d,e){a.success&&a.success.call(a.context,e,d,b);a.global&&c.triggerGlobal(a,"ajaxSuccess",[b,a])},handleComplete:function(a,
4010+b,d){a.complete&&a.complete.call(a.context,b,d);a.global&&c.triggerGlobal(a,"ajaxComplete",[b,a]);a.global&&c.active--===1&&c.event.trigger("ajaxStop")},triggerGlobal:function(a,b,d){(a.context&&a.context.url==null?c(a.context):c.event).trigger(b,d)},httpSuccess:function(a){try{return!a.status&&location.protocol==="file:"||a.status>=200&&a.status<300||a.status===304||a.status===1223}catch(b){}return false},httpNotModified:function(a,b){var d=a.getResponseHeader("Last-Modified"),e=a.getResponseHeader("Etag");
4011+if(d)c.lastModified[b]=d;if(e)c.etag[b]=e;return a.status===304},httpData:function(a,b,d){var e=a.getResponseHeader("content-type")||"",f=b==="xml"||!b&&e.indexOf("xml")>=0;a=f?a.responseXML:a.responseText;f&&a.documentElement.nodeName==="parsererror"&&c.error("parsererror");if(d&&d.dataFilter)a=d.dataFilter(a,b);if(typeof a==="string")if(b==="json"||!b&&e.indexOf("json")>=0)a=c.parseJSON(a);else if(b==="script"||!b&&e.indexOf("javascript")>=0)c.globalEval(a);return a}});if(E.ActiveXObject)c.ajaxSettings.xhr=
4012+function(){if(E.location.protocol!=="file:")try{return new E.XMLHttpRequest}catch(a){}try{return new E.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}};c.support.ajax=!!c.ajaxSettings.xhr();var da={},tb=/^(?:toggle|show|hide)$/,ub=/^([+\-]=)?([\d+.\-]+)(.*)$/,aa,na=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]];c.fn.extend({show:function(a,b,d){if(a||a===0)return this.animate(S("show",3),a,b,d);else{a=
4013+0;for(b=this.length;a<b;a++){if(!c.data(this[a],"olddisplay")&&this[a].style.display==="none")this[a].style.display="";this[a].style.display===""&&c.css(this[a],"display")==="none"&&c.data(this[a],"olddisplay",oa(this[a].nodeName))}for(a=0;a<b;a++)this[a].style.display=c.data(this[a],"olddisplay")||"";return this}},hide:function(a,b,d){if(a||a===0)return this.animate(S("hide",3),a,b,d);else{a=0;for(b=this.length;a<b;a++){d=c.css(this[a],"display");d!=="none"&&c.data(this[a],"olddisplay",d)}for(a=
4014+0;a<b;a++)this[a].style.display="none";return this}},_toggle:c.fn.toggle,toggle:function(a,b,d){var e=typeof a==="boolean";if(c.isFunction(a)&&c.isFunction(b))this._toggle.apply(this,arguments);else a==null||e?this.each(function(){var f=e?a:c(this).is(":hidden");c(this)[f?"show":"hide"]()}):this.animate(S("toggle",3),a,b,d);return this},fadeTo:function(a,b,d,e){return this.filter(":hidden").css("opacity",0).show().end().animate({opacity:b},a,d,e)},animate:function(a,b,d,e){var f=c.speed(b,d,e);if(c.isEmptyObject(a))return this.each(f.complete);
4015+return this[f.queue===false?"each":"queue"](function(){var h=c.extend({},f),k,l=this.nodeType===1,n=l&&c(this).is(":hidden"),s=this;for(k in a){var v=c.camelCase(k);if(k!==v){a[v]=a[k];delete a[k];k=v}if(a[k]==="hide"&&n||a[k]==="show"&&!n)return h.complete.call(this);if(l&&(k==="height"||k==="width")){h.overflow=[this.style.overflow,this.style.overflowX,this.style.overflowY];if(c.css(this,"display")==="inline"&&c.css(this,"float")==="none")if(c.support.inlineBlockNeedsLayout)if(oa(this.nodeName)===
4016+"inline")this.style.display="inline-block";else{this.style.display="inline";this.style.zoom=1}else this.style.display="inline-block"}if(c.isArray(a[k])){(h.specialEasing=h.specialEasing||{})[k]=a[k][1];a[k]=a[k][0]}}if(h.overflow!=null)this.style.overflow="hidden";h.curAnim=c.extend({},a);c.each(a,function(B,D){var H=new c.fx(s,h,B);if(tb.test(D))H[D==="toggle"?n?"show":"hide":D](a);else{var w=ub.exec(D),G=H.cur(true)||0;if(w){var M=parseFloat(w[2]),g=w[3]||"px";if(g!=="px"){c.style(s,B,(M||1)+g);
4017+G=(M||1)/H.cur(true)*G;c.style(s,B,G+g)}if(w[1])M=(w[1]==="-="?-1:1)*M+G;H.custom(G,M,g)}else H.custom(G,D,"")}});return true})},stop:function(a,b){var d=c.timers;a&&this.queue([]);this.each(function(){for(var e=d.length-1;e>=0;e--)if(d[e].elem===this){b&&d[e](true);d.splice(e,1)}});b||this.dequeue();return this}});c.each({slideDown:S("show",1),slideUp:S("hide",1),slideToggle:S("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"}},function(a,b){c.fn[a]=function(d,e,f){return this.animate(b,
4018+d,e,f)}});c.extend({speed:function(a,b,d){var e=a&&typeof a==="object"?c.extend({},a):{complete:d||!d&&b||c.isFunction(a)&&a,duration:a,easing:d&&b||b&&!c.isFunction(b)&&b};e.duration=c.fx.off?0:typeof e.duration==="number"?e.duration:e.duration in c.fx.speeds?c.fx.speeds[e.duration]:c.fx.speeds._default;e.old=e.complete;e.complete=function(){e.queue!==false&&c(this).dequeue();c.isFunction(e.old)&&e.old.call(this)};return e},easing:{linear:function(a,b,d,e){return d+e*a},swing:function(a,b,d,e){return(-Math.cos(a*
4019+Math.PI)/2+0.5)*e+d}},timers:[],fx:function(a,b,d){this.options=b;this.elem=a;this.prop=d;if(!b.orig)b.orig={}}});c.fx.prototype={update:function(){this.options.step&&this.options.step.call(this.elem,this.now,this);(c.fx.step[this.prop]||c.fx.step._default)(this)},cur:function(){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null))return this.elem[this.prop];var a=parseFloat(c.css(this.elem,this.prop));return a&&a>-1E4?a:0},custom:function(a,b,d){function e(h){return f.step(h)}
4020+this.startTime=c.now();this.start=a;this.end=b;this.unit=d||this.unit||"px";this.now=this.start;this.pos=this.state=0;var f=this;a=c.fx;e.elem=this.elem;if(e()&&c.timers.push(e)&&!aa)aa=setInterval(a.tick,a.interval)},show:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.show=true;this.custom(this.prop==="width"||this.prop==="height"?1:0,this.cur());c(this.elem).show()},hide:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.hide=true;
4021+this.custom(this.cur(),0)},step:function(a){var b=c.now(),d=true;if(a||b>=this.options.duration+this.startTime){this.now=this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;for(var e in this.options.curAnim)if(this.options.curAnim[e]!==true)d=false;if(d){if(this.options.overflow!=null&&!c.support.shrinkWrapBlocks){var f=this.elem,h=this.options;c.each(["","X","Y"],function(l,n){f.style["overflow"+n]=h.overflow[l]})}this.options.hide&&c(this.elem).hide();if(this.options.hide||
4022+this.options.show)for(var k in this.options.curAnim)c.style(this.elem,k,this.options.orig[k]);this.options.complete.call(this.elem)}return false}else{a=b-this.startTime;this.state=a/this.options.duration;b=this.options.easing||(c.easing.swing?"swing":"linear");this.pos=c.easing[this.options.specialEasing&&this.options.specialEasing[this.prop]||b](this.state,a,0,1,this.options.duration);this.now=this.start+(this.end-this.start)*this.pos;this.update()}return true}};c.extend(c.fx,{tick:function(){for(var a=
4023+c.timers,b=0;b<a.length;b++)a[b]()||a.splice(b--,1);a.length||c.fx.stop()},interval:13,stop:function(){clearInterval(aa);aa=null},speeds:{slow:600,fast:200,_default:400},step:{opacity:function(a){c.style(a.elem,"opacity",a.now)},_default:function(a){if(a.elem.style&&a.elem.style[a.prop]!=null)a.elem.style[a.prop]=(a.prop==="width"||a.prop==="height"?Math.max(0,a.now):a.now)+a.unit;else a.elem[a.prop]=a.now}}});if(c.expr&&c.expr.filters)c.expr.filters.animated=function(a){return c.grep(c.timers,function(b){return a===
4024+b.elem}).length};var vb=/^t(?:able|d|h)$/i,Fa=/^(?:body|html)$/i;c.fn.offset="getBoundingClientRect"in u.documentElement?function(a){var b=this[0],d;if(a)return this.each(function(k){c.offset.setOffset(this,a,k)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return c.offset.bodyOffset(b);try{d=b.getBoundingClientRect()}catch(e){}var f=b.ownerDocument,h=f.documentElement;if(!d||!c.contains(h,b))return d||{top:0,left:0};b=f.body;f=ea(f);return{top:d.top+(f.pageYOffset||c.support.boxModel&&
4025+h.scrollTop||b.scrollTop)-(h.clientTop||b.clientTop||0),left:d.left+(f.pageXOffset||c.support.boxModel&&h.scrollLeft||b.scrollLeft)-(h.clientLeft||b.clientLeft||0)}}:function(a){var b=this[0];if(a)return this.each(function(s){c.offset.setOffset(this,a,s)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return c.offset.bodyOffset(b);c.offset.initialize();var d=b.offsetParent,e=b.ownerDocument,f,h=e.documentElement,k=e.body;f=(e=e.defaultView)?e.getComputedStyle(b,null):b.currentStyle;
4026+for(var l=b.offsetTop,n=b.offsetLeft;(b=b.parentNode)&&b!==k&&b!==h;){if(c.offset.supportsFixedPosition&&f.position==="fixed")break;f=e?e.getComputedStyle(b,null):b.currentStyle;l-=b.scrollTop;n-=b.scrollLeft;if(b===d){l+=b.offsetTop;n+=b.offsetLeft;if(c.offset.doesNotAddBorder&&!(c.offset.doesAddBorderForTableAndCells&&vb.test(b.nodeName))){l+=parseFloat(f.borderTopWidth)||0;n+=parseFloat(f.borderLeftWidth)||0}d=b.offsetParent}if(c.offset.subtractsBorderForOverflowNotVisible&&f.overflow!=="visible"){l+=
4027+parseFloat(f.borderTopWidth)||0;n+=parseFloat(f.borderLeftWidth)||0}f=f}if(f.position==="relative"||f.position==="static"){l+=k.offsetTop;n+=k.offsetLeft}if(c.offset.supportsFixedPosition&&f.position==="fixed"){l+=Math.max(h.scrollTop,k.scrollTop);n+=Math.max(h.scrollLeft,k.scrollLeft)}return{top:l,left:n}};c.offset={initialize:function(){var a=u.body,b=u.createElement("div"),d,e,f,h=parseFloat(c.css(a,"marginTop"))||0;c.extend(b.style,{position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",
4028+height:"1px",visibility:"hidden"});b.innerHTML="<div style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;'><div></div></div><table style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;' cellpadding='0' cellspacing='0'><tr><td></td></tr></table>";a.insertBefore(b,a.firstChild);d=b.firstChild;e=d.firstChild;f=d.nextSibling.firstChild.firstChild;this.doesNotAddBorder=e.offsetTop!==5;this.doesAddBorderForTableAndCells=
4029+f.offsetTop===5;e.style.position="fixed";e.style.top="20px";this.supportsFixedPosition=e.offsetTop===20||e.offsetTop===15;e.style.position=e.style.top="";d.style.overflow="hidden";d.style.position="relative";this.subtractsBorderForOverflowNotVisible=e.offsetTop===-5;this.doesNotIncludeMarginInBodyOffset=a.offsetTop!==h;a.removeChild(b);c.offset.initialize=c.noop},bodyOffset:function(a){var b=a.offsetTop,d=a.offsetLeft;c.offset.initialize();if(c.offset.doesNotIncludeMarginInBodyOffset){b+=parseFloat(c.css(a,
4030+"marginTop"))||0;d+=parseFloat(c.css(a,"marginLeft"))||0}return{top:b,left:d}},setOffset:function(a,b,d){var e=c.css(a,"position");if(e==="static")a.style.position="relative";var f=c(a),h=f.offset(),k=c.css(a,"top"),l=c.css(a,"left"),n=e==="absolute"&&c.inArray("auto",[k,l])>-1;e={};var s={};if(n)s=f.position();k=n?s.top:parseInt(k,10)||0;l=n?s.left:parseInt(l,10)||0;if(c.isFunction(b))b=b.call(a,d,h);if(b.top!=null)e.top=b.top-h.top+k;if(b.left!=null)e.left=b.left-h.left+l;"using"in b?b.using.call(a,
4031+e):f.css(e)}};c.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),d=this.offset(),e=Fa.test(b[0].nodeName)?{top:0,left:0}:b.offset();d.top-=parseFloat(c.css(a,"marginTop"))||0;d.left-=parseFloat(c.css(a,"marginLeft"))||0;e.top+=parseFloat(c.css(b[0],"borderTopWidth"))||0;e.left+=parseFloat(c.css(b[0],"borderLeftWidth"))||0;return{top:d.top-e.top,left:d.left-e.left}},offsetParent:function(){return this.map(function(){for(var a=this.offsetParent||u.body;a&&!Fa.test(a.nodeName)&&
4032+c.css(a,"position")==="static";)a=a.offsetParent;return a})}});c.each(["Left","Top"],function(a,b){var d="scroll"+b;c.fn[d]=function(e){var f=this[0],h;if(!f)return null;if(e!==A)return this.each(function(){if(h=ea(this))h.scrollTo(!a?e:c(h).scrollLeft(),a?e:c(h).scrollTop());else this[d]=e});else return(h=ea(f))?"pageXOffset"in h?h[a?"pageYOffset":"pageXOffset"]:c.support.boxModel&&h.document.documentElement[d]||h.document.body[d]:f[d]}});c.each(["Height","Width"],function(a,b){var d=b.toLowerCase();
4033+c.fn["inner"+b]=function(){return this[0]?parseFloat(c.css(this[0],d,"padding")):null};c.fn["outer"+b]=function(e){return this[0]?parseFloat(c.css(this[0],d,e?"margin":"border")):null};c.fn[d]=function(e){var f=this[0];if(!f)return e==null?null:this;if(c.isFunction(e))return this.each(function(h){var k=c(this);k[d](e.call(this,h,k[d]()))});return c.isWindow(f)?f.document.compatMode==="CSS1Compat"&&f.document.documentElement["client"+b]||f.document.body["client"+b]:f.nodeType===9?Math.max(f.documentElement["client"+
4034+b],f.body["scroll"+b],f.documentElement["scroll"+b],f.body["offset"+b],f.documentElement["offset"+b]):e===A?parseFloat(c.css(f,d)):this.css(d,typeof e==="string"?e:e+"px")}})})(window);
4035
4036=== added directory 'webapp/graphite/graphlot'
4037=== added file 'webapp/graphite/graphlot/__init__.py'
4038=== added file 'webapp/graphite/graphlot/urls.py'
4039--- webapp/graphite/graphlot/urls.py 1970-01-01 00:00:00 +0000
4040+++ webapp/graphite/graphlot/urls.py 2010-11-01 18:48:50 +0000
4041@@ -0,0 +1,21 @@
4042+"""Copyright 2008 Orbitz WorldWide
4043+
4044+Licensed under the Apache License, Version 2.0 (the "License");
4045+you may not use this file except in compliance with the License.
4046+You may obtain a copy of the License at
4047+
4048+ http://www.apache.org/licenses/LICENSE-2.0
4049+
4050+ Unless required by applicable law or agreed to in writing, software
4051+ distributed under the License is distributed on an "AS IS" BASIS,
4052+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
4053+ See the License for the specific language governing permissions and
4054+ limitations under the License."""
4055+
4056+from django.conf.urls.defaults import *
4057+
4058+urlpatterns = patterns('graphite.graphlot.views',
4059+ ('^rawdata$', 'get_data'),
4060+ ('^findmetric$', 'find_metric'),
4061+ ('^graphlot$', 'graphlot_render'),
4062+)
4063
4064=== added file 'webapp/graphite/graphlot/views.py'
4065--- webapp/graphite/graphlot/views.py 1970-01-01 00:00:00 +0000
4066+++ webapp/graphite/graphlot/views.py 2010-11-01 18:48:50 +0000
4067@@ -0,0 +1,252 @@
4068+import re
4069+
4070+from django.shortcuts import render_to_response
4071+from django.http import HttpResponse, Http404
4072+from django.conf import settings
4073+import simplejson
4074+
4075+from graphite.render.views import parseOptions
4076+from graphite.render.evaluator import evaluateTarget
4077+
4078+
4079+def graphlot_render(request):
4080+ """Render the main graphlot view."""
4081+ metrics = []
4082+ for target in request.GET.getlist('target'):
4083+ metrics.append(dict(name=target, yaxis="one"))
4084+ for target in request.GET.getlist('y2target'):
4085+ metrics.append(dict(name=target, yaxis="two"))
4086+
4087+ untiltime = request.GET.get('until', "-0hour")
4088+ fromtime = request.GET.get('from', "-24hour")
4089+ context = dict(metric_list=metrics, fromtime=fromtime, untiltime=untiltime)
4090+ return render_to_response("graphlot.html", context)
4091+
4092+def get_data(request):
4093+ """Get the data for one series."""
4094+ (graphOptions, requestOptions) = parseOptions(request)
4095+ requestContext = {
4096+ 'startTime' : requestOptions['startTime'],
4097+ 'endTime' : requestOptions['endTime'],
4098+ 'data' : []
4099+ }
4100+ target = requestOptions['targets'][0]
4101+ seriesList = evaluateTarget(requestContext, target)
4102+ result = [ dict(
4103+ name=timeseries.name,
4104+ data=[ x for x in timeseries ],
4105+ start=timeseries.start,
4106+ end=timeseries.end,
4107+ step=timeseries.step,
4108+ ) for timeseries in seriesList ]
4109+ if not result:
4110+ raise Http404
4111+ return HttpResponse(simplejson.dumps(result), mimetype="application/json")
4112+
4113+def find_metric(request):
4114+ """Autocomplete helper on metric names."""
4115+ try:
4116+ query = str( request.REQUEST['q'] )
4117+ except:
4118+ return HttpResponseBadRequest(
4119+ content="Missing required parameter 'q'", mimetype="text/plain")
4120+
4121+ store = settings.LOCAL_STORE
4122+
4123+ matches = list( store.find(query+"*") )
4124+ content = "\n".join([node.metric_path for node in matches ])
4125+ response = HttpResponse(content, mimetype='text/plain')
4126+
4127+ return response
4128+
4129+def header(request):
4130+ "View for the header frame of the browser UI"
4131+ context = {}
4132+ context['user'] = request.user
4133+ context['profile'] = getProfile(request)
4134+ context['documentation_url'] = settings.DOCUMENTATION_URL
4135+ return render_to_response("browserHeader.html", context)
4136+
4137+
4138+def browser(request):
4139+ "View for the top-level frame of the browser UI"
4140+ context = {
4141+ 'queryString' : request.GET.urlencode(),
4142+ 'target' : request.GET.get('target')
4143+ }
4144+ if context['queryString']:
4145+ context['queryString'] = context['queryString'].replace('#','%23')
4146+ if context['target']:
4147+ context['target'] = context['target'].replace('#','%23') #js libs terminate a querystring on #
4148+ return render_to_response("browser.html", context)
4149+
4150+
4151+def search(request):
4152+ query = request.POST['query']
4153+ if not query:
4154+ return HttpResponse("")
4155+
4156+ patterns = query.split()
4157+ regexes = [re.compile(p,re.I) for p in patterns]
4158+ def matches(s):
4159+ for regex in regexes:
4160+ if regex.search(s):
4161+ return True
4162+ return False
4163+
4164+ results = []
4165+
4166+ index_file = open(settings.INDEX_FILE)
4167+ for line in index_file:
4168+ if matches(line):
4169+ results.append( line.strip() )
4170+ if len(results) >= 100:
4171+ break
4172+
4173+ index_file.close()
4174+ result_string = ','.join(results)
4175+ return HttpResponse(result_string, mimetype='text/plain')
4176+
4177+
4178+def myGraphLookup(request):
4179+ "View for My Graphs navigation"
4180+ profile = getProfile(request,allowDefault=False)
4181+ assert profile
4182+
4183+ nodes = []
4184+ leafNode = {
4185+ 'allowChildren' : 0,
4186+ 'expandable' : 0,
4187+ 'leaf' : 1,
4188+ }
4189+ branchNode = {
4190+ 'allowChildren' : 1,
4191+ 'expandable' : 1,
4192+ 'leaf' : 0,
4193+ }
4194+
4195+ try:
4196+ path = str( request.GET['path'] )
4197+
4198+ if path:
4199+ if path.endswith('.'):
4200+ userpath_prefix = path
4201+
4202+ else:
4203+ userpath_prefix = path + '.'
4204+
4205+ else:
4206+ userpath_prefix = ""
4207+
4208+ matches = [ graph for graph in profile.mygraph_set.all().order_by('name') if graph.name.startswith(userpath_prefix) ]
4209+
4210+ log.info( "myGraphLookup: username=%s, path=%s, userpath_prefix=%s, %ld graph to process" % (profile.user.username, path, userpath_prefix, len(matches)) )
4211+ branch_inserted = set()
4212+ leaf_inserted = set()
4213+
4214+ for graph in matches: #Now let's add the matching graph
4215+ isBranch = False
4216+ dotPos = graph.name.find( '.', len(userpath_prefix) )
4217+
4218+ if dotPos >= 0:
4219+ isBranch = True
4220+ name = graph.name[ len(userpath_prefix) : dotPos ]
4221+ if name in branch_inserted: continue
4222+ branch_inserted.add(name)
4223+
4224+ else:
4225+ name = graph.name[ len(userpath_prefix): ]
4226+ if name in leaf_inserted: continue
4227+ leaf_inserted.add(name)
4228+
4229+ node = {'text' : str(name) }
4230+
4231+ if isBranch:
4232+ node.update( { 'id' : str(userpath_prefix + name + '.') } )
4233+ node.update(branchNode)
4234+
4235+ else:
4236+ node.update( { 'id' : str(userpath_prefix + name), 'graphUrl' : str(graph.url) } )
4237+ node.update(leafNode)
4238+
4239+ nodes.append(node)
4240+
4241+ except:
4242+ log.exception("browser.views.myGraphLookup(): could not complete request.")
4243+
4244+ if not nodes:
4245+ no_graphs = { 'text' : "No saved graphs", 'id' : 'no-click' }
4246+ no_graphs.update(leafNode)
4247+ nodes.append(no_graphs)
4248+
4249+ return json_response(nodes)
4250+
4251+def userGraphLookup(request):
4252+ "View for User Graphs navigation"
4253+ username = request.GET['path']
4254+ nodes = []
4255+
4256+ branchNode = {
4257+ 'allowChildren' : 1,
4258+ 'expandable' : 1,
4259+ 'leaf' : 0,
4260+ }
4261+ leafNode = {
4262+ 'allowChildren' : 0,
4263+ 'expandable' : 0,
4264+ 'leaf' : 1,
4265+ }
4266+
4267+ try:
4268+
4269+ if not username:
4270+ profiles = Profile.objects.exclude(user=defaultUser)
4271+
4272+ for profile in profiles:
4273+ if profile.mygraph_set.count():
4274+ node = {
4275+ 'text' : str(profile.user.username),
4276+ 'id' : str(profile.user.username)
4277+ }
4278+
4279+ node.update(branchNode)
4280+ nodes.append(node)
4281+
4282+ else:
4283+ profile = getProfileByUsername(username)
4284+ assert profile, "No profile for username '%s'" % username
4285+
4286+ for graph in profile.mygraph_set.all().order_by('name'):
4287+ node = {
4288+ 'text' : str(graph.name),
4289+ 'id' : str(graph.name),
4290+ 'graphUrl' : str(graph.url)
4291+ }
4292+ node.update(leafNode)
4293+ nodes.append(node)
4294+
4295+ except:
4296+ log.exception("browser.views.userLookup(): could not complete request for %s" % username)
4297+
4298+ if not nodes:
4299+ no_graphs = { 'text' : "No saved graphs", 'id' : 'no-click' }
4300+ no_graphs.update(leafNode)
4301+ nodes.append(no_graphs)
4302+
4303+ return json_response(nodes)
4304+
4305+
4306+def json_response(nodes):
4307+ #json = str(nodes) #poor man's json encoder for simple types
4308+ json_data = json.dumps(nodes)
4309+ response = HttpResponse(json_data,mimetype="application/json")
4310+ response['Pragma'] = 'no-cache'
4311+ response['Cache-Control'] = 'no-cache'
4312+ return response
4313+
4314+
4315+def any(iterable): #python2.4 compatibility
4316+ for i in iterable:
4317+ if i:
4318+ return True
4319+ return False
4320
4321=== added file 'webapp/graphite/templates/graphlot.html'
4322--- webapp/graphite/templates/graphlot.html 1970-01-01 00:00:00 +0000
4323+++ webapp/graphite/templates/graphlot.html 2010-11-01 18:48:50 +0000
4324@@ -0,0 +1,99 @@
4325+{% autoescape off %}
4326+
4327+<html>
4328+ <head>
4329+ <title>Graphlot</title>
4330+ <script type="text/javascript" src="../content/js/jquery.js"></script>
4331+ <script type="text/javascript" src="../content/js/jquery.flot.js"></script>
4332+ <script type="text/javascript" src="../content/js/jquery.autocomplete.js"></script>
4333+ <script type="text/javascript" src="../content/js/jquery.flot.selection.js"></script>
4334+ <script type="text/javascript" src="../content/js/jquery.flot.crosshair.js"></script>
4335+ <script type="text/javascript" src="../content/js/jquery.graphite.js"></script>
4336+
4337+ <link rel="stylesheet" type="text/css" href="../content/css/jquery.autocomplete.css" />
4338+ <link rel="stylesheet" type="text/css" href="../content/css/table.css" />
4339+
4340+ <script type="text/javascript">
4341+
4342+ $(document).ready(function () {
4343+ $('.graphite').graphiteGraph();
4344+ });
4345+
4346+ </script>
4347+
4348+
4349+ <style type="text/css">
4350+ body {
4351+ font-family: sans-serif;
4352+ font-size: 16px;
4353+ margin: 50px;
4354+ max-width: 1200px;
4355+ }
4356+
4357+ .ajaxerror {
4358+ background: #ff0000;
4359+ }
4360+ .ajaxworking {
4361+ background: #0000ff;
4362+ }
4363+ </style>
4364+
4365+
4366+ </head>
4367+ <body>
4368+ <div id="title" style="text-align:center">
4369+ <h1>graphlot</h1>
4370+ </div>
4371+ <div class="graphite">
4372+ <div id="main" >
4373+ <div id="canvas" style="padding:15px">
4374+ <div id="graphcontainer" style=float:left;">
4375+ <div id="graph" style="width:600px;height:300px"></div>
4376+ <div id="overview" style="width:600px;height:66px"></div>
4377+ </div>
4378+ <div id="side" style="float:left">
4379+ <p id="legend" style="margin-left:10px"></p>
4380+ </div>
4381+ <p style="clear:left">&nbsp</p>
4382+ </div>
4383+ <br/>
4384+
4385+ <table id="rowlist" class="styledtable" style="float:left">
4386+ <tr><th style="width:750px">metric</th><th>y-axis</th></tr>
4387+ {% for metric in metric_list %}
4388+ <tr class="metricrow">
4389+ <td><a href=#><span class="metricName">{{metric.name}}</span></a>
4390+ </td>
4391+ <td><a href=#><span class="yaxis">{{metric.yaxis}}</span></a>
4392+ </td>
4393+ <td class="killrow"><img src="../content/img/delete.png">
4394+ </td>
4395+ </tr>
4396+ {% endfor %}
4397+
4398+ <tr id="newmetricrow"><td><input style="width:600px" type="text" id="newmetric"/></td></tr>
4399+ </table>
4400+
4401+ <table id="bounds" class="styledtable" style="float:right">
4402+ <tr><th>From</th><th>To</th></tr>
4403+ <tr>
4404+ <td><a href=#><span id="from">{{fromtime}}</span></a></td>
4405+ <td><a href=#><span id="until">{{untiltime}}</span></a></td>
4406+ </tr>
4407+ <tr> <td colspan="2" style="text-align:center">
4408+ <a href=#><span id="update">update</span></a>
4409+ </td></tr>
4410+ <tr> <td colspan="2" style="text-align:center">
4411+ <a href=#><span id="clearzoom">clear zoom</span></a>
4412+ </td></tr>
4413+ <tr> <td colspan="2" style="text-align:center">
4414+ <a href=# id="graphurl">link to graph</a>
4415+ </td></tr>
4416+ </table>
4417+ </div>
4418+
4419+ </div>
4420+ </body>
4421+</html>
4422+
4423+{% endautoescape %}
4424\ No newline at end of file
4425
4426=== modified file 'webapp/graphite/urls.py'
4427--- webapp/graphite/urls.py 2009-11-12 03:35:45 +0000
4428+++ webapp/graphite/urls.py 2010-11-01 18:48:50 +0000
4429@@ -28,6 +28,7 @@
4430 ('^account/?', include('graphite.account.urls')),
4431 ('^whitelist/?', include('graphite.whitelist.urls')),
4432 ('^content/(?P<path>.*)$', 'django.views.static.serve', {'document_root' : settings.CONTENT_DIR}),
4433+ ('', include('graphite.graphlot.urls')),
4434 ('', include('graphite.browser.urls')),
4435 )
4436

Subscribers

People subscribed via source and target branches