cobweb 0.0.38 → 0.0.39

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (160) hide show
  1. data/README.textile +1 -1
  2. data/lib/cobweb.rb +3 -1
  3. data/lib/cobweb_crawler.rb +98 -100
  4. data/lib/crawl_job.rb +3 -4
  5. data/lib/server.rb +98 -0
  6. data/lib/stats.rb +141 -64
  7. data/public/css/accordion.css +45 -0
  8. data/public/css/custom.css +13 -0
  9. data/public/css/datatable.css +189 -0
  10. data/public/css/datepicker.css +171 -0
  11. data/public/css/form-buttons.css +180 -0
  12. data/public/css/forms.css +489 -0
  13. data/public/css/jquery.fancybox-1.3.4.css +282 -0
  14. data/public/css/jquery.treeview.css +103 -0
  15. data/public/css/link-buttons.css +187 -0
  16. data/public/css/login.css +110 -0
  17. data/public/css/menu.css +156 -0
  18. data/public/css/messages.css +58 -0
  19. data/public/css/modalbox.css +63 -0
  20. data/public/css/statics.css +57 -0
  21. data/public/css/style.css +497 -0
  22. data/public/css/style_text.css +128 -0
  23. data/public/css/tabs.css +58 -0
  24. data/public/css/wysiwyg-editor.css +18 -0
  25. data/public/css/wysiwyg.css +149 -0
  26. data/public/css/wysiwyg.modal.css +69 -0
  27. data/public/gfx/back-menu.gif +0 -0
  28. data/public/gfx/back-submenu.gif +0 -0
  29. data/public/gfx/background.gif +0 -0
  30. data/public/gfx/box-hide.png +0 -0
  31. data/public/gfx/box-search.png +0 -0
  32. data/public/gfx/box-title.gif +0 -0
  33. data/public/gfx/code.gif +0 -0
  34. data/public/gfx/datepicker-arrows.gif +0 -0
  35. data/public/gfx/fancybox/blank.gif +0 -0
  36. data/public/gfx/fancybox/fancy_close.png +0 -0
  37. data/public/gfx/fancybox/fancy_loading.png +0 -0
  38. data/public/gfx/fancybox/fancy_nav_left.png +0 -0
  39. data/public/gfx/fancybox/fancy_nav_right.png +0 -0
  40. data/public/gfx/fancybox/fancy_title_left.png +0 -0
  41. data/public/gfx/fancybox/fancy_title_main.png +0 -0
  42. data/public/gfx/fancybox/fancy_title_over.png +0 -0
  43. data/public/gfx/fancybox/fancy_title_right.png +0 -0
  44. data/public/gfx/fancybox/fancybox-x.png +0 -0
  45. data/public/gfx/fancybox/fancybox.png +0 -0
  46. data/public/gfx/forms/date-next.gif +0 -0
  47. data/public/gfx/forms/date-prev.gif +0 -0
  48. data/public/gfx/forms/forms-checkbox.gif +0 -0
  49. data/public/gfx/forms/forms-date.gif +0 -0
  50. data/public/gfx/forms/forms-file.gif +0 -0
  51. data/public/gfx/forms/forms-input-big.gif +0 -0
  52. data/public/gfx/forms/forms-input-medium.gif +0 -0
  53. data/public/gfx/forms/forms-input-small.gif +0 -0
  54. data/public/gfx/forms/forms-input-xl.gif +0 -0
  55. data/public/gfx/forms/forms-radio.gif +0 -0
  56. data/public/gfx/forms/forms-selectbox-small.gif +0 -0
  57. data/public/gfx/forms/forms-selectbox.gif +0 -0
  58. data/public/gfx/forms/forms-textarea-big.gif +0 -0
  59. data/public/gfx/forms/forms-textarea-medium.gif +0 -0
  60. data/public/gfx/forms/forms-textarea-small.gif +0 -0
  61. data/public/gfx/forms/forms-textarea-xl.gif +0 -0
  62. data/public/gfx/icon-delete.png +0 -0
  63. data/public/gfx/icon-edit.png +0 -0
  64. data/public/gfx/icon-home.gif +0 -0
  65. data/public/gfx/img-delete.png +0 -0
  66. data/public/gfx/img-hover.png +0 -0
  67. data/public/gfx/img-zoom.png +0 -0
  68. data/public/gfx/jquery.wysiwyg.gif +0 -0
  69. data/public/gfx/label-icons.gif +0 -0
  70. data/public/gfx/label.gif +0 -0
  71. data/public/gfx/li-down.gif +0 -0
  72. data/public/gfx/li.gif +0 -0
  73. data/public/gfx/link-button-big.gif +0 -0
  74. data/public/gfx/link-button-medium.gif +0 -0
  75. data/public/gfx/link-button.gif +0 -0
  76. data/public/gfx/loading-2.gif +0 -0
  77. data/public/gfx/loading.gif +0 -0
  78. data/public/gfx/logo.png +0 -0
  79. data/public/gfx/modal-title.gif +0 -0
  80. data/public/gfx/photos/00.jpg +0 -0
  81. data/public/gfx/photos/01.jpg +0 -0
  82. data/public/gfx/photos/01xl.jpg +0 -0
  83. data/public/gfx/photos/02.jpg +0 -0
  84. data/public/gfx/photos/02xl.jpg +0 -0
  85. data/public/gfx/photos/03.jpg +0 -0
  86. data/public/gfx/photos/03xl.jpg +0 -0
  87. data/public/gfx/photos/04.jpg +0 -0
  88. data/public/gfx/photos/04xl.jpg +0 -0
  89. data/public/gfx/photos/05.jpg +0 -0
  90. data/public/gfx/photos/05xl.jpg +0 -0
  91. data/public/gfx/photos/06.jpg +0 -0
  92. data/public/gfx/photos/06xl.jpg +0 -0
  93. data/public/gfx/photos/07.jpg +0 -0
  94. data/public/gfx/photos/07xl.jpg +0 -0
  95. data/public/gfx/photos/08.jpg +0 -0
  96. data/public/gfx/photos/08xl.jpg +0 -0
  97. data/public/gfx/photos/09.jpg +0 -0
  98. data/public/gfx/photos/09xl.jpg +0 -0
  99. data/public/gfx/photos/10.jpg +0 -0
  100. data/public/gfx/photos/10xl.jpg +0 -0
  101. data/public/gfx/photos/11.jpg +0 -0
  102. data/public/gfx/photos/11xl.jpg +0 -0
  103. data/public/gfx/photos/12.jpg +0 -0
  104. data/public/gfx/photos/12xl.jpg +0 -0
  105. data/public/gfx/photos/13.jpg +0 -0
  106. data/public/gfx/photos/13xl.jpg +0 -0
  107. data/public/gfx/photos/14.jpg +0 -0
  108. data/public/gfx/photos/14xl.jpg +0 -0
  109. data/public/gfx/photos/15.jpg +0 -0
  110. data/public/gfx/photos/15xl.jpg +0 -0
  111. data/public/gfx/search-button.gif +0 -0
  112. data/public/gfx/search-input.gif +0 -0
  113. data/public/gfx/slider-button.gif +0 -0
  114. data/public/gfx/system-messages.gif +0 -0
  115. data/public/gfx/table-asc-arrow.gif +0 -0
  116. data/public/gfx/table-desc-arrow.gif +0 -0
  117. data/public/gfx/table-first.gif +0 -0
  118. data/public/gfx/table-last.gif +0 -0
  119. data/public/gfx/table-next.gif +0 -0
  120. data/public/gfx/table-number.gif +0 -0
  121. data/public/gfx/table-prev.gif +0 -0
  122. data/public/gfx/table-rows.gif +0 -0
  123. data/public/gfx/table-search.gif +0 -0
  124. data/public/gfx/table-thead.gif +0 -0
  125. data/public/gfx/tooltip.gif +0 -0
  126. data/public/gfx/treeview/ajax-loader.gif +0 -0
  127. data/public/gfx/treeview/file.gif +0 -0
  128. data/public/gfx/treeview/folder-closed.gif +0 -0
  129. data/public/gfx/treeview/folder.gif +0 -0
  130. data/public/gfx/treeview/minus.gif +0 -0
  131. data/public/gfx/treeview/plus.gif +0 -0
  132. data/public/gfx/treeview/treeview-default-line.gif +0 -0
  133. data/public/gfx/treeview/treeview-default.gif +0 -0
  134. data/public/js/controls/wysiwyg.image.js +284 -0
  135. data/public/js/controls/wysiwyg.link.js +210 -0
  136. data/public/js/controls/wysiwyg.table.js +151 -0
  137. data/public/js/customInput.jquery.js +68 -0
  138. data/public/js/excanvas.min.js +1 -0
  139. data/public/js/hoverIntent.js +84 -0
  140. data/public/js/inline.js +392 -0
  141. data/public/js/jquery-1.7.1.min.js +4 -0
  142. data/public/js/jquery-ui-select.js +522 -0
  143. data/public/js/jquery-ui-timepicker-addon.js +1299 -0
  144. data/public/js/jquery-ui.js +791 -0
  145. data/public/js/jquery.dataTables.js +7440 -0
  146. data/public/js/jquery.fancybox-1.3.4.js +1156 -0
  147. data/public/js/jquery.filestyle.mini.js +2 -0
  148. data/public/js/jquery.flot.js +2600 -0
  149. data/public/js/jquery.flot.resize.min.js +60 -0
  150. data/public/js/jquery.graphtable-0.2.js +179 -0
  151. data/public/js/jquery.tipsy.js +104 -0
  152. data/public/js/jquery.treeview.js +256 -0
  153. data/public/js/jquery.wysiwyg.js +2454 -0
  154. data/public/js/plugins/wysiwyg.rmFormat.js +348 -0
  155. data/public/js/superfish.js +121 -0
  156. data/public/js/supersubs.js +90 -0
  157. data/views/home.haml +54 -0
  158. data/views/layout.haml +89 -0
  159. data/views/statistics.haml +251 -71
  160. metadata +175 -22
@@ -0,0 +1,2 @@
1
+
2
+ (function($){$.fn.filestyle=function(options){var settings={width:250};if(options){$.extend(settings,options);};return this.each(function(){var self=this;var wrapper=$("<div class='btn-upload'>").css({"width":settings.imagewidth+"px","height":settings.imageheight+"px","margin":"0","overflow":"hidden"});var filename=$('<input class="file">').addClass($(self).attr("class")).css({"width":settings.width+"px"});$(self).before(filename);$(self).wrap(wrapper);$(self).css({"height":settings.imageheight+"px","width":settings.width+"px","display":"inline","cursor":"pointer","opacity":"0.0"});if($.browser.mozilla){if(/Win/.test(navigator.platform)){$(self).css("margin-left","-142px");}else{$(self).css("margin-left","-168px");};}else{$(self).css("margin-left",settings.imagewidth-settings.width+"px");};$(self).bind("change",function(){var s = $(self).val().replace(/(c:\\)*fakepath/i, '');filename.val(s);});});};})(jQuery);
@@ -0,0 +1,2600 @@
1
+ /*! Javascript plotting library for jQuery, v. 0.7.
2
+ *
3
+ * Released under the MIT license by IOLA, December 2007.
4
+ *
5
+ */
6
+
7
+ // first an inline dependency, jquery.colorhelpers.js, we inline it here
8
+ // for convenience
9
+
10
+ /* Plugin for jQuery for working with colors.
11
+ *
12
+ * Version 1.1.
13
+ *
14
+ * Inspiration from jQuery color animation plugin by John Resig.
15
+ *
16
+ * Released under the MIT license by Ole Laursen, October 2009.
17
+ *
18
+ * Examples:
19
+ *
20
+ * $.color.parse("#fff").scale('rgb', 0.25).add('a', -0.5).toString()
21
+ * var c = $.color.extract($("#mydiv"), 'background-color');
22
+ * console.log(c.r, c.g, c.b, c.a);
23
+ * $.color.make(100, 50, 25, 0.4).toString() // returns "rgba(100,50,25,0.4)"
24
+ *
25
+ * Note that .scale() and .add() return the same modified object
26
+ * instead of making a new one.
27
+ *
28
+ * V. 1.1: Fix error handling so e.g. parsing an empty string does
29
+ * produce a color rather than just crashing.
30
+ */
31
+ (function(B){B.color={};B.color.make=function(F,E,C,D){var G={};G.r=F||0;G.g=E||0;G.b=C||0;G.a=D!=null?D:1;G.add=function(J,I){for(var H=0;H<J.length;++H){G[J.charAt(H)]+=I}return G.normalize()};G.scale=function(J,I){for(var H=0;H<J.length;++H){G[J.charAt(H)]*=I}return G.normalize()};G.toString=function(){if(G.a>=1){return"rgb("+[G.r,G.g,G.b].join(",")+")"}else{return"rgba("+[G.r,G.g,G.b,G.a].join(",")+")"}};G.normalize=function(){function H(J,K,I){return K<J?J:(K>I?I:K)}G.r=H(0,parseInt(G.r),255);G.g=H(0,parseInt(G.g),255);G.b=H(0,parseInt(G.b),255);G.a=H(0,G.a,1);return G};G.clone=function(){return B.color.make(G.r,G.b,G.g,G.a)};return G.normalize()};B.color.extract=function(D,C){var E;do{E=D.css(C).toLowerCase();if(E!=""&&E!="transparent"){break}D=D.parent()}while(!B.nodeName(D.get(0),"body"));if(E=="rgba(0, 0, 0, 0)"){E="transparent"}return B.color.parse(E)};B.color.parse=function(F){var E,C=B.color.make;if(E=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(F)){return C(parseInt(E[1],10),parseInt(E[2],10),parseInt(E[3],10))}if(E=/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(F)){return C(parseInt(E[1],10),parseInt(E[2],10),parseInt(E[3],10),parseFloat(E[4]))}if(E=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(F)){return C(parseFloat(E[1])*2.55,parseFloat(E[2])*2.55,parseFloat(E[3])*2.55)}if(E=/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(F)){return C(parseFloat(E[1])*2.55,parseFloat(E[2])*2.55,parseFloat(E[3])*2.55,parseFloat(E[4]))}if(E=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(F)){return C(parseInt(E[1],16),parseInt(E[2],16),parseInt(E[3],16))}if(E=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(F)){return C(parseInt(E[1]+E[1],16),parseInt(E[2]+E[2],16),parseInt(E[3]+E[3],16))}var D=B.trim(F).toLowerCase();if(D=="transparent"){return C(255,255,255,0)}else{E=A[D]||[0,0,0];return C(E[0],E[1],E[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]}})(jQuery);
32
+
33
+ // the actual Flot code
34
+ (function($) {
35
+ function Plot(placeholder, data_, options_, plugins) {
36
+ // data is on the form:
37
+ // [ series1, series2 ... ]
38
+ // where series is either just the data as [ [x1, y1], [x2, y2], ... ]
39
+ // or { data: [ [x1, y1], [x2, y2], ... ], label: "some label", ... }
40
+
41
+ var series = [],
42
+ options = {
43
+ // the color theme used for graphs
44
+ colors: ["#92d5ea", "#666699", "#be1e2d", "#ee8310", "#8d10ee"],
45
+ legend: {
46
+ show: true,
47
+ noColumns: 0, // number of colums in legend table
48
+ labelFormatter: null, // fn: string -> string
49
+ labelBoxBorderColor: "none", // border color for the little label boxes
50
+ container: null, // container (as jQuery object) to put legend in, null means default on top of graph
51
+ position: "ne", // position of default legend container within plot
52
+ margin: 0, // distance from grid edge to default legend container within plot
53
+ backgroundColor: null, // null means auto-detect
54
+ backgroundOpacity: 0 // set to 0 to avoid background
55
+ },
56
+ xaxis: {
57
+ show: null, // null = auto-detect, true = always, false = never
58
+ position: "bottom", // or "top"
59
+ mode: null, // null or "time"
60
+ color: null, // base color, labels, ticks
61
+ tickColor: null, // possibly different color of ticks, e.g. "rgba(0,0,0,0.15)"
62
+ transform: null, // null or f: number -> number to transform axis
63
+ inverseTransform: null, // if transform is set, this should be the inverse function
64
+ min: null, // min. value to show, null means set automatically
65
+ max: null, // max. value to show, null means set automatically
66
+ autoscaleMargin: null, // margin in % to add if auto-setting min/max
67
+ ticks: null, // either [1, 3] or [[1, "a"], 3] or (fn: axis info -> ticks) or app. number of ticks for auto-ticks
68
+ tickFormatter: null, // fn: number -> string
69
+ labelWidth: null, // size of tick labels in pixels
70
+ labelHeight: null,
71
+ reserveSpace: null, // whether to reserve space even if axis isn't shown
72
+ tickLength: null, // size in pixels of ticks, or "full" for whole line
73
+ alignTicksWithAxis: null, // axis number or null for no sync
74
+
75
+ // mode specific options
76
+ tickDecimals: 0, // no. of decimals, null means auto
77
+ tickSize: null, // number or [number, "unit"]
78
+ minTickSize: null, // number or [number, "unit"]
79
+ monthNames: null, // list of names of months
80
+ timeformat: null, // format string to use
81
+ twelveHourClock: false // 12 or 24 time in time mode
82
+ },
83
+ yaxis: {
84
+ autoscaleMargin: 0.02,
85
+ position: "left" // or "right"
86
+ },
87
+ xaxes: [],
88
+ yaxes: [],
89
+ series: {
90
+ points: {
91
+ show: true,
92
+ radius: 3,
93
+ lineWidth: 2, // in pixels
94
+ fill: true,
95
+ fillColor: "#ffffff",
96
+ symbol: "circle" // or callback
97
+ },
98
+ lines: {
99
+ // we don't put in show: false so we can see
100
+ // whether lines were actively disabled
101
+ lineWidth: 2, // in pixels
102
+ fill: true,
103
+ fillColor: null,
104
+ steps: false,
105
+ show : true
106
+ },
107
+ bars: {
108
+ show: false,
109
+ lineWidth: 2, // in pixels
110
+ barWidth: 1, // in units of the x axis
111
+ fill: true,
112
+ fillColor: null,
113
+ align: "left", // or "center"
114
+ horizontal: false
115
+ },
116
+ shadowSize: 3
117
+ },
118
+ grid: {
119
+ show: true,
120
+ aboveData: false,
121
+ color: "#545454", // primary color used for outline and labels
122
+ backgroundColor: null, // null for transparent, else color
123
+ borderColor: null, // set if different from the grid color
124
+ tickColor: null, // color for the ticks, e.g. "rgba(0,0,0,0.15)"
125
+ labelMargin: 5, // in pixels
126
+ axisMargin: 8, // in pixels
127
+ borderWidth: 1, // in pixels
128
+ minBorderMargin: null, // in pixels, null means taken from points radius
129
+ markings: null, // array of ranges or fn: axes -> array of ranges
130
+ markingsColor: "#f4f4f4",
131
+ markingsLineWidth: 2,
132
+ // interactive stuff
133
+ clickable: false,
134
+ hoverable: true,
135
+ autoHighlight: true, // highlight in case mouse is near
136
+ mouseActiveRadius: 5 // how far the mouse can be away to activate an item
137
+ },
138
+ hooks: {}
139
+ },
140
+ canvas = null, // the canvas for the plot itself
141
+ overlay = null, // canvas for interactive stuff on top of plot
142
+ eventHolder = null, // jQuery object that events should be bound to
143
+ ctx = null, octx = null,
144
+ xaxes = [], yaxes = [],
145
+ plotOffset = { left: 0, right: 0, top: 0, bottom: 0},
146
+ canvasWidth = 0, canvasHeight = 0,
147
+ plotWidth = 0, plotHeight = 0,
148
+ hooks = {
149
+ processOptions: [],
150
+ processRawData: [],
151
+ processDatapoints: [],
152
+ drawSeries: [],
153
+ draw: [],
154
+ bindEvents: [],
155
+ drawOverlay: [],
156
+ shutdown: []
157
+ },
158
+ plot = this;
159
+
160
+ // public functions
161
+ plot.setData = setData;
162
+ plot.setupGrid = setupGrid;
163
+ plot.draw = draw;
164
+ plot.getPlaceholder = function() { return placeholder; };
165
+ plot.getCanvas = function() { return canvas; };
166
+ plot.getPlotOffset = function() { return plotOffset; };
167
+ plot.width = function () { return plotWidth; };
168
+ plot.height = function () { return plotHeight; };
169
+ plot.offset = function () {
170
+ var o = eventHolder.offset();
171
+ o.left += plotOffset.left;
172
+ o.top += plotOffset.top;
173
+ return o;
174
+ };
175
+ plot.getData = function () { return series; };
176
+ plot.getAxes = function () {
177
+ var res = {}, i;
178
+ $.each(xaxes.concat(yaxes), function (_, axis) {
179
+ if (axis)
180
+ res[axis.direction + (axis.n != 1 ? axis.n : "") + "axis"] = axis;
181
+ });
182
+ return res;
183
+ };
184
+ plot.getXAxes = function () { return xaxes; };
185
+ plot.getYAxes = function () { return yaxes; };
186
+ plot.c2p = canvasToAxisCoords;
187
+ plot.p2c = axisToCanvasCoords;
188
+ plot.getOptions = function () { return options; };
189
+ plot.highlight = highlight;
190
+ plot.unhighlight = unhighlight;
191
+ plot.triggerRedrawOverlay = triggerRedrawOverlay;
192
+ plot.pointOffset = function(point) {
193
+ return {
194
+ left: parseInt(xaxes[axisNumber(point, "x") - 1].p2c(+point.x) + plotOffset.left),
195
+ top: parseInt(yaxes[axisNumber(point, "y") - 1].p2c(+point.y) + plotOffset.top)
196
+ };
197
+ };
198
+ plot.shutdown = shutdown;
199
+ plot.resize = function () {
200
+ getCanvasDimensions();
201
+ resizeCanvas(canvas);
202
+ resizeCanvas(overlay);
203
+ };
204
+
205
+ // public attributes
206
+ plot.hooks = hooks;
207
+
208
+ // initialize
209
+ initPlugins(plot);
210
+ parseOptions(options_);
211
+ setupCanvases();
212
+ setData(data_);
213
+ setupGrid();
214
+ draw();
215
+ bindEvents();
216
+
217
+
218
+ function executeHooks(hook, args) {
219
+ args = [plot].concat(args);
220
+ for (var i = 0; i < hook.length; ++i)
221
+ hook[i].apply(this, args);
222
+ }
223
+
224
+ function initPlugins() {
225
+ for (var i = 0; i < plugins.length; ++i) {
226
+ var p = plugins[i];
227
+ p.init(plot);
228
+ if (p.options)
229
+ $.extend(true, options, p.options);
230
+ }
231
+ }
232
+
233
+ function parseOptions(opts) {
234
+ var i;
235
+
236
+ $.extend(true, options, opts);
237
+
238
+ if (options.xaxis.color == null)
239
+ options.xaxis.color = options.grid.color;
240
+ if (options.yaxis.color == null)
241
+ options.yaxis.color = options.grid.color;
242
+
243
+ if (options.xaxis.tickColor == null) // backwards-compatibility
244
+ options.xaxis.tickColor = options.grid.tickColor;
245
+ if (options.yaxis.tickColor == null) // backwards-compatibility
246
+ options.yaxis.tickColor = options.grid.tickColor;
247
+
248
+ if (options.grid.borderColor == null)
249
+ options.grid.borderColor = options.grid.color;
250
+ if (options.grid.tickColor == null)
251
+ options.grid.tickColor = $.color.parse(options.grid.color).scale('a', 0.22).toString();
252
+
253
+ // fill in defaults in axes, copy at least always the
254
+ // first as the rest of the code assumes it'll be there
255
+ for (i = 0; i < Math.max(1, options.xaxes.length); ++i)
256
+ options.xaxes[i] = $.extend(true, {}, options.xaxis, options.xaxes[i]);
257
+ for (i = 0; i < Math.max(1, options.yaxes.length); ++i)
258
+ options.yaxes[i] = $.extend(true, {}, options.yaxis, options.yaxes[i]);
259
+
260
+ // backwards compatibility, to be removed in future
261
+ if (options.xaxis.noTicks && options.xaxis.ticks == null)
262
+ options.xaxis.ticks = options.xaxis.noTicks;
263
+ if (options.yaxis.noTicks && options.yaxis.ticks == null)
264
+ options.yaxis.ticks = options.yaxis.noTicks;
265
+ if (options.x2axis) {
266
+ options.xaxes[1] = $.extend(true, {}, options.xaxis, options.x2axis);
267
+ options.xaxes[1].position = "top";
268
+ }
269
+ if (options.y2axis) {
270
+ options.yaxes[1] = $.extend(true, {}, options.yaxis, options.y2axis);
271
+ options.yaxes[1].position = "right";
272
+ }
273
+ if (options.grid.coloredAreas)
274
+ options.grid.markings = options.grid.coloredAreas;
275
+ if (options.grid.coloredAreasColor)
276
+ options.grid.markingsColor = options.grid.coloredAreasColor;
277
+ if (options.lines)
278
+ $.extend(true, options.series.lines, options.lines);
279
+ if (options.points)
280
+ $.extend(true, options.series.points, options.points);
281
+ if (options.bars)
282
+ $.extend(true, options.series.bars, options.bars);
283
+ if (options.shadowSize != null)
284
+ options.series.shadowSize = options.shadowSize;
285
+
286
+ // save options on axes for future reference
287
+ for (i = 0; i < options.xaxes.length; ++i)
288
+ getOrCreateAxis(xaxes, i + 1).options = options.xaxes[i];
289
+ for (i = 0; i < options.yaxes.length; ++i)
290
+ getOrCreateAxis(yaxes, i + 1).options = options.yaxes[i];
291
+
292
+ // add hooks from options
293
+ for (var n in hooks)
294
+ if (options.hooks[n] && options.hooks[n].length)
295
+ hooks[n] = hooks[n].concat(options.hooks[n]);
296
+
297
+ executeHooks(hooks.processOptions, [options]);
298
+ }
299
+
300
+ function setData(d) {
301
+ series = parseData(d);
302
+ fillInSeriesOptions();
303
+ processData();
304
+ }
305
+
306
+ function parseData(d) {
307
+ var res = [];
308
+ for (var i = 0; i < d.length; ++i) {
309
+ var s = $.extend(true, {}, options.series);
310
+
311
+ if (d[i].data != null) {
312
+ s.data = d[i].data; // move the data instead of deep-copy
313
+ delete d[i].data;
314
+
315
+ $.extend(true, s, d[i]);
316
+
317
+ d[i].data = s.data;
318
+ }
319
+ else
320
+ s.data = d[i];
321
+ res.push(s);
322
+ }
323
+
324
+ return res;
325
+ }
326
+
327
+ function axisNumber(obj, coord) {
328
+ var a = obj[coord + "axis"];
329
+ if (typeof a == "object") // if we got a real axis, extract number
330
+ a = a.n;
331
+ if (typeof a != "number")
332
+ a = 1; // default to first axis
333
+ return a;
334
+ }
335
+
336
+ function allAxes() {
337
+ // return flat array without annoying null entries
338
+ return $.grep(xaxes.concat(yaxes), function (a) { return a; });
339
+ }
340
+
341
+ function canvasToAxisCoords(pos) {
342
+ // return an object with x/y corresponding to all used axes
343
+ var res = {}, i, axis;
344
+ for (i = 0; i < xaxes.length; ++i) {
345
+ axis = xaxes[i];
346
+ if (axis && axis.used)
347
+ res["x" + axis.n] = axis.c2p(pos.left);
348
+ }
349
+
350
+ for (i = 0; i < yaxes.length; ++i) {
351
+ axis = yaxes[i];
352
+ if (axis && axis.used)
353
+ res["y" + axis.n] = axis.c2p(pos.top);
354
+ }
355
+
356
+ if (res.x1 !== undefined)
357
+ res.x = res.x1;
358
+ if (res.y1 !== undefined)
359
+ res.y = res.y1;
360
+
361
+ return res;
362
+ }
363
+
364
+ function axisToCanvasCoords(pos) {
365
+ // get canvas coords from the first pair of x/y found in pos
366
+ var res = {}, i, axis, key;
367
+
368
+ for (i = 0; i < xaxes.length; ++i) {
369
+ axis = xaxes[i];
370
+ if (axis && axis.used) {
371
+ key = "x" + axis.n;
372
+ if (pos[key] == null && axis.n == 1)
373
+ key = "x";
374
+
375
+ if (pos[key] != null) {
376
+ res.left = axis.p2c(pos[key]);
377
+ break;
378
+ }
379
+ }
380
+ }
381
+
382
+ for (i = 0; i < yaxes.length; ++i) {
383
+ axis = yaxes[i];
384
+ if (axis && axis.used) {
385
+ key = "y" + axis.n;
386
+ if (pos[key] == null && axis.n == 1)
387
+ key = "y";
388
+
389
+ if (pos[key] != null) {
390
+ res.top = axis.p2c(pos[key]);
391
+ break;
392
+ }
393
+ }
394
+ }
395
+
396
+ return res;
397
+ }
398
+
399
+ function getOrCreateAxis(axes, number) {
400
+ if (!axes[number - 1])
401
+ axes[number - 1] = {
402
+ n: number, // save the number for future reference
403
+ direction: axes == xaxes ? "x" : "y",
404
+ options: $.extend(true, {}, axes == xaxes ? options.xaxis : options.yaxis)
405
+ };
406
+
407
+ return axes[number - 1];
408
+ }
409
+
410
+ function fillInSeriesOptions() {
411
+ var i;
412
+
413
+ // collect what we already got of colors
414
+ var neededColors = series.length,
415
+ usedColors = [],
416
+ assignedColors = [];
417
+ for (i = 0; i < series.length; ++i) {
418
+ var sc = series[i].color;
419
+ if (sc != null) {
420
+ --neededColors;
421
+ if (typeof sc == "number")
422
+ assignedColors.push(sc);
423
+ else
424
+ usedColors.push($.color.parse(series[i].color));
425
+ }
426
+ }
427
+
428
+ // we might need to generate more colors if higher indices
429
+ // are assigned
430
+ for (i = 0; i < assignedColors.length; ++i) {
431
+ neededColors = Math.max(neededColors, assignedColors[i] + 1);
432
+ }
433
+
434
+ // produce colors as needed
435
+ var colors = [], variation = 0;
436
+ i = 0;
437
+ while (colors.length < neededColors) {
438
+ var c;
439
+ if (options.colors.length == i) // check degenerate case
440
+ c = $.color.make(100, 100, 100);
441
+ else
442
+ c = $.color.parse(options.colors[i]);
443
+
444
+ // vary color if needed
445
+ var sign = variation % 2 == 1 ? -1 : 1;
446
+ c.scale('rgb', 1 + sign * Math.ceil(variation / 2) * 0.2)
447
+
448
+ // FIXME: if we're getting to close to something else,
449
+ // we should probably skip this one
450
+ colors.push(c);
451
+
452
+ ++i;
453
+ if (i >= options.colors.length) {
454
+ i = 0;
455
+ ++variation;
456
+ }
457
+ }
458
+
459
+ // fill in the options
460
+ var colori = 0, s;
461
+ for (i = 0; i < series.length; ++i) {
462
+ s = series[i];
463
+
464
+ // assign colors
465
+ if (s.color == null) {
466
+ s.color = colors[colori].toString();
467
+ ++colori;
468
+ }
469
+ else if (typeof s.color == "number")
470
+ s.color = colors[s.color].toString();
471
+
472
+ // turn on lines automatically in case nothing is set
473
+ if (s.lines.show == null) {
474
+ var v, show = true;
475
+ for (v in s)
476
+ if (s[v] && s[v].show) {
477
+ show = false;
478
+ break;
479
+ }
480
+ if (show)
481
+ s.lines.show = true;
482
+ }
483
+
484
+ // setup axes
485
+ s.xaxis = getOrCreateAxis(xaxes, axisNumber(s, "x"));
486
+ s.yaxis = getOrCreateAxis(yaxes, axisNumber(s, "y"));
487
+ }
488
+ }
489
+
490
+ function processData() {
491
+ var topSentry = Number.POSITIVE_INFINITY,
492
+ bottomSentry = Number.NEGATIVE_INFINITY,
493
+ fakeInfinity = Number.MAX_VALUE,
494
+ i, j, k, m, length,
495
+ s, points, ps, x, y, axis, val, f, p;
496
+
497
+ function updateAxis(axis, min, max) {
498
+ if (min < axis.datamin && min != -fakeInfinity)
499
+ axis.datamin = min;
500
+ if (max > axis.datamax && max != fakeInfinity)
501
+ axis.datamax = max;
502
+ }
503
+
504
+ $.each(allAxes(), function (_, axis) {
505
+ // init axis
506
+ axis.datamin = topSentry;
507
+ axis.datamax = bottomSentry;
508
+ axis.used = false;
509
+ });
510
+
511
+ for (i = 0; i < series.length; ++i) {
512
+ s = series[i];
513
+ s.datapoints = { points: [] };
514
+
515
+ executeHooks(hooks.processRawData, [ s, s.data, s.datapoints ]);
516
+ }
517
+
518
+ // first pass: clean and copy data
519
+ for (i = 0; i < series.length; ++i) {
520
+ s = series[i];
521
+
522
+ var data = s.data, format = s.datapoints.format;
523
+
524
+ if (!format) {
525
+ format = [];
526
+ // find out how to copy
527
+ format.push({ x: true, number: true, required: true });
528
+ format.push({ y: true, number: true, required: true });
529
+
530
+ if (s.bars.show || (s.lines.show && s.lines.fill)) {
531
+ format.push({ y: true, number: true, required: false, defaultValue: 0 });
532
+ if (s.bars.horizontal) {
533
+ delete format[format.length - 1].y;
534
+ format[format.length - 1].x = true;
535
+ }
536
+ }
537
+
538
+ s.datapoints.format = format;
539
+ }
540
+
541
+ if (s.datapoints.pointsize != null)
542
+ continue; // already filled in
543
+
544
+ s.datapoints.pointsize = format.length;
545
+
546
+ ps = s.datapoints.pointsize;
547
+ points = s.datapoints.points;
548
+
549
+ insertSteps = s.lines.show && s.lines.steps;
550
+ s.xaxis.used = s.yaxis.used = true;
551
+
552
+ for (j = k = 0; j < data.length; ++j, k += ps) {
553
+ p = data[j];
554
+
555
+ var nullify = p == null;
556
+ if (!nullify) {
557
+ for (m = 0; m < ps; ++m) {
558
+ val = p[m];
559
+ f = format[m];
560
+
561
+ if (f) {
562
+ if (f.number && val != null) {
563
+ val = +val; // convert to number
564
+ if (isNaN(val))
565
+ val = null;
566
+ else if (val == Infinity)
567
+ val = fakeInfinity;
568
+ else if (val == -Infinity)
569
+ val = -fakeInfinity;
570
+ }
571
+
572
+ if (val == null) {
573
+ if (f.required)
574
+ nullify = true;
575
+
576
+ if (f.defaultValue != null)
577
+ val = f.defaultValue;
578
+ }
579
+ }
580
+
581
+ points[k + m] = val;
582
+ }
583
+ }
584
+
585
+ if (nullify) {
586
+ for (m = 0; m < ps; ++m) {
587
+ val = points[k + m];
588
+ if (val != null) {
589
+ f = format[m];
590
+ // extract min/max info
591
+ if (f.x)
592
+ updateAxis(s.xaxis, val, val);
593
+ if (f.y)
594
+ updateAxis(s.yaxis, val, val);
595
+ }
596
+ points[k + m] = null;
597
+ }
598
+ }
599
+ else {
600
+ // a little bit of line specific stuff that
601
+ // perhaps shouldn't be here, but lacking
602
+ // better means...
603
+ if (insertSteps && k > 0
604
+ && points[k - ps] != null
605
+ && points[k - ps] != points[k]
606
+ && points[k - ps + 1] != points[k + 1]) {
607
+ // copy the point to make room for a middle point
608
+ for (m = 0; m < ps; ++m)
609
+ points[k + ps + m] = points[k + m];
610
+
611
+ // middle point has same y
612
+ points[k + 1] = points[k - ps + 1];
613
+
614
+ // we've added a point, better reflect that
615
+ k += ps;
616
+ }
617
+ }
618
+ }
619
+ }
620
+
621
+ // give the hooks a chance to run
622
+ for (i = 0; i < series.length; ++i) {
623
+ s = series[i];
624
+
625
+ executeHooks(hooks.processDatapoints, [ s, s.datapoints]);
626
+ }
627
+
628
+ // second pass: find datamax/datamin for auto-scaling
629
+ for (i = 0; i < series.length; ++i) {
630
+ s = series[i];
631
+ points = s.datapoints.points,
632
+ ps = s.datapoints.pointsize;
633
+
634
+ var xmin = topSentry, ymin = topSentry,
635
+ xmax = bottomSentry, ymax = bottomSentry;
636
+
637
+ for (j = 0; j < points.length; j += ps) {
638
+ if (points[j] == null)
639
+ continue;
640
+
641
+ for (m = 0; m < ps; ++m) {
642
+ val = points[j + m];
643
+ f = format[m];
644
+ if (!f || val == fakeInfinity || val == -fakeInfinity)
645
+ continue;
646
+
647
+ if (f.x) {
648
+ if (val < xmin)
649
+ xmin = val;
650
+ if (val > xmax)
651
+ xmax = val;
652
+ }
653
+ if (f.y) {
654
+ if (val < ymin)
655
+ ymin = val;
656
+ if (val > ymax)
657
+ ymax = val;
658
+ }
659
+ }
660
+ }
661
+
662
+ if (s.bars.show) {
663
+ // make sure we got room for the bar on the dancing floor
664
+ var delta = s.bars.align == "left" ? 0 : -s.bars.barWidth/2;
665
+ if (s.bars.horizontal) {
666
+ ymin += delta;
667
+ ymax += delta + s.bars.barWidth;
668
+ }
669
+ else {
670
+ xmin += delta;
671
+ xmax += delta + s.bars.barWidth;
672
+ }
673
+ }
674
+
675
+ updateAxis(s.xaxis, xmin, xmax);
676
+ updateAxis(s.yaxis, ymin, ymax);
677
+ }
678
+
679
+ $.each(allAxes(), function (_, axis) {
680
+ if (axis.datamin == topSentry)
681
+ axis.datamin = null;
682
+ if (axis.datamax == bottomSentry)
683
+ axis.datamax = null;
684
+ });
685
+ }
686
+
687
+ function makeCanvas(skipPositioning, cls) {
688
+ var c = document.createElement('canvas');
689
+ c.className = cls;
690
+ c.width = canvasWidth;
691
+ c.height = canvasHeight;
692
+
693
+ if (!skipPositioning)
694
+ $(c).css({ position: 'absolute', left: 0, top: 0 });
695
+
696
+ $(c).appendTo(placeholder);
697
+
698
+ if (!c.getContext) // excanvas hack
699
+ c = window.G_vmlCanvasManager.initElement(c);
700
+
701
+ // used for resetting in case we get replotted
702
+ c.getContext("2d").save();
703
+
704
+ return c;
705
+ }
706
+
707
+ function getCanvasDimensions() {
708
+ canvasWidth = placeholder.width();
709
+ canvasHeight = placeholder.height();
710
+
711
+ if (canvasWidth <= 0 || canvasHeight <= 0)
712
+ throw "Invalid dimensions for plot, width = " + canvasWidth + ", height = " + canvasHeight;
713
+ }
714
+
715
+ function resizeCanvas(c) {
716
+ // resizing should reset the state (excanvas seems to be
717
+ // buggy though)
718
+ if (c.width != canvasWidth)
719
+ c.width = canvasWidth;
720
+
721
+ if (c.height != canvasHeight)
722
+ c.height = canvasHeight;
723
+
724
+ // so try to get back to the initial state (even if it's
725
+ // gone now, this should be safe according to the spec)
726
+ var cctx = c.getContext("2d");
727
+ cctx.restore();
728
+
729
+ // and save again
730
+ cctx.save();
731
+ }
732
+
733
+ function setupCanvases() {
734
+ var reused,
735
+ existingCanvas = placeholder.children("canvas.base"),
736
+ existingOverlay = placeholder.children("canvas.overlay");
737
+
738
+ if (existingCanvas.length == 0 || existingOverlay == 0) {
739
+ // init everything
740
+
741
+ placeholder.html(""); // make sure placeholder is clear
742
+
743
+ placeholder.css({ padding: 0 }); // padding messes up the positioning
744
+
745
+ if (placeholder.css("position") == 'static')
746
+ placeholder.css("position", "relative"); // for positioning labels and overlay
747
+
748
+ getCanvasDimensions();
749
+
750
+ canvas = makeCanvas(true, "base");
751
+ overlay = makeCanvas(false, "overlay"); // overlay canvas for interactive features
752
+
753
+ reused = false;
754
+ }
755
+ else {
756
+ // reuse existing elements
757
+
758
+ canvas = existingCanvas.get(0);
759
+ overlay = existingOverlay.get(0);
760
+
761
+ reused = true;
762
+ }
763
+
764
+ ctx = canvas.getContext("2d");
765
+ octx = overlay.getContext("2d");
766
+
767
+ // we include the canvas in the event holder too, because IE 7
768
+ // sometimes has trouble with the stacking order
769
+ eventHolder = $([overlay, canvas]);
770
+
771
+ if (reused) {
772
+ // run shutdown in the old plot object
773
+ placeholder.data("plot").shutdown();
774
+
775
+ // reset reused canvases
776
+ plot.resize();
777
+
778
+ // make sure overlay pixels are cleared (canvas is cleared when we redraw)
779
+ octx.clearRect(0, 0, canvasWidth, canvasHeight);
780
+
781
+ // then whack any remaining obvious garbage left
782
+ eventHolder.unbind();
783
+ placeholder.children().not([canvas, overlay]).remove();
784
+ }
785
+
786
+ // save in case we get replotted
787
+ placeholder.data("plot", plot);
788
+ }
789
+
790
+ function bindEvents() {
791
+ // bind events
792
+ if (options.grid.hoverable) {
793
+ eventHolder.mousemove(onMouseMove);
794
+ eventHolder.mouseleave(onMouseLeave);
795
+ }
796
+
797
+ if (options.grid.clickable)
798
+ eventHolder.click(onClick);
799
+
800
+ executeHooks(hooks.bindEvents, [eventHolder]);
801
+ }
802
+
803
+ function shutdown() {
804
+ if (redrawTimeout)
805
+ clearTimeout(redrawTimeout);
806
+
807
+ eventHolder.unbind("mousemove", onMouseMove);
808
+ eventHolder.unbind("mouseleave", onMouseLeave);
809
+ eventHolder.unbind("click", onClick);
810
+
811
+ executeHooks(hooks.shutdown, [eventHolder]);
812
+ }
813
+
814
+ function setTransformationHelpers(axis) {
815
+ // set helper functions on the axis, assumes plot area
816
+ // has been computed already
817
+
818
+ function identity(x) { return x; }
819
+
820
+ var s, m, t = axis.options.transform || identity,
821
+ it = axis.options.inverseTransform;
822
+
823
+ // precompute how much the axis is scaling a point
824
+ // in canvas space
825
+ if (axis.direction == "x") {
826
+ s = axis.scale = plotWidth / Math.abs(t(axis.max) - t(axis.min));
827
+ m = Math.min(t(axis.max), t(axis.min));
828
+ }
829
+ else {
830
+ s = axis.scale = plotHeight / Math.abs(t(axis.max) - t(axis.min));
831
+ s = -s;
832
+ m = Math.max(t(axis.max), t(axis.min));
833
+ }
834
+
835
+ // data point to canvas coordinate
836
+ if (t == identity) // slight optimization
837
+ axis.p2c = function (p) { return (p - m) * s; };
838
+ else
839
+ axis.p2c = function (p) { return (t(p) - m) * s; };
840
+ // canvas coordinate to data point
841
+ if (!it)
842
+ axis.c2p = function (c) { return m + c / s; };
843
+ else
844
+ axis.c2p = function (c) { return it(m + c / s); };
845
+ }
846
+
847
+ function measureTickLabels(axis) {
848
+ var opts = axis.options, i, ticks = axis.ticks || [], labels = [],
849
+ l, w = opts.labelWidth, h = opts.labelHeight, dummyDiv;
850
+
851
+ function makeDummyDiv(labels, width) {
852
+ return $('<div style="position:absolute;top:-10000px;' + width + 'font-size:smaller">' +
853
+ '<div class="' + axis.direction + 'Axis ' + axis.direction + axis.n + 'Axis">'
854
+ + labels.join("") + '</div></div>')
855
+ .appendTo(placeholder);
856
+ }
857
+
858
+ if (axis.direction == "x") {
859
+ // to avoid measuring the widths of the labels (it's slow), we
860
+ // construct fixed-size boxes and put the labels inside
861
+ // them, we don't need the exact figures and the
862
+ // fixed-size box content is easy to center
863
+ if (w == null)
864
+ w = Math.floor(canvasWidth / (ticks.length > 0 ? ticks.length : 1));
865
+
866
+ // measure x label heights
867
+ if (h == null) {
868
+ labels = [];
869
+ for (i = 0; i < ticks.length; ++i) {
870
+ l = ticks[i].label;
871
+ if (l)
872
+ labels.push('<div class="tickLabel" style="float:left;width:' + w + 'px">' + l + '</div>');
873
+ }
874
+
875
+ if (labels.length > 0) {
876
+ // stick them all in the same div and measure
877
+ // collective height
878
+ labels.push('<div style="clear:left"></div>');
879
+ dummyDiv = makeDummyDiv(labels, "width:10000px;");
880
+ h = dummyDiv.height();
881
+ dummyDiv.remove();
882
+ }
883
+ }
884
+ }
885
+ else if (w == null || h == null) {
886
+ // calculate y label dimensions
887
+ for (i = 0; i < ticks.length; ++i) {
888
+ l = ticks[i].label;
889
+ if (l)
890
+ labels.push('<div class="tickLabel">' + l + '</div>');
891
+ }
892
+
893
+ if (labels.length > 0) {
894
+ dummyDiv = makeDummyDiv(labels, "");
895
+ if (w == null)
896
+ w = dummyDiv.children().width();
897
+ if (h == null)
898
+ h = dummyDiv.find("div.tickLabel").height();
899
+ dummyDiv.remove();
900
+ }
901
+ }
902
+
903
+ if (w == null)
904
+ w = 0;
905
+ if (h == null)
906
+ h = 0;
907
+
908
+ axis.labelWidth = w;
909
+ axis.labelHeight = h;
910
+ }
911
+
912
+ function allocateAxisBoxFirstPhase(axis) {
913
+ // find the bounding box of the axis by looking at label
914
+ // widths/heights and ticks, make room by diminishing the
915
+ // plotOffset
916
+
917
+ var lw = axis.labelWidth,
918
+ lh = axis.labelHeight,
919
+ pos = axis.options.position,
920
+ tickLength = axis.options.tickLength,
921
+ axismargin = options.grid.axisMargin,
922
+ padding = options.grid.labelMargin,
923
+ all = axis.direction == "x" ? xaxes : yaxes,
924
+ index;
925
+
926
+ // determine axis margin
927
+ var samePosition = $.grep(all, function (a) {
928
+ return a && a.options.position == pos && a.reserveSpace;
929
+ });
930
+ if ($.inArray(axis, samePosition) == samePosition.length - 1)
931
+ axismargin = 0; // outermost
932
+
933
+ // determine tick length - if we're innermost, we can use "full"
934
+ if (tickLength == null)
935
+ tickLength = "full";
936
+
937
+ var sameDirection = $.grep(all, function (a) {
938
+ return a && a.reserveSpace;
939
+ });
940
+
941
+ var innermost = $.inArray(axis, sameDirection) == 0;
942
+ if (!innermost && tickLength == "full")
943
+ tickLength = 5;
944
+
945
+ if (!isNaN(+tickLength))
946
+ padding += +tickLength;
947
+
948
+ // compute box
949
+ if (axis.direction == "x") {
950
+ lh += padding;
951
+
952
+ if (pos == "bottom") {
953
+ plotOffset.bottom += lh + axismargin;
954
+ axis.box = { top: canvasHeight - plotOffset.bottom, height: lh };
955
+ }
956
+ else {
957
+ axis.box = { top: plotOffset.top + axismargin, height: lh };
958
+ plotOffset.top += lh + axismargin;
959
+ }
960
+ }
961
+ else {
962
+ lw += padding;
963
+
964
+ if (pos == "left") {
965
+ axis.box = { left: plotOffset.left + axismargin, width: lw };
966
+ plotOffset.left += lw + axismargin;
967
+ }
968
+ else {
969
+ plotOffset.right += lw + axismargin;
970
+ axis.box = { left: canvasWidth - plotOffset.right, width: lw };
971
+ }
972
+ }
973
+
974
+ // save for future reference
975
+ axis.position = pos;
976
+ axis.tickLength = tickLength;
977
+ axis.box.padding = padding;
978
+ axis.innermost = innermost;
979
+ }
980
+
981
+ function allocateAxisBoxSecondPhase(axis) {
982
+ // set remaining bounding box coordinates
983
+ if (axis.direction == "x") {
984
+ axis.box.left = plotOffset.left;
985
+ axis.box.width = plotWidth;
986
+ }
987
+ else {
988
+ axis.box.top = plotOffset.top;
989
+ axis.box.height = plotHeight;
990
+ }
991
+ }
992
+
993
+ function setupGrid() {
994
+ var i, axes = allAxes();
995
+
996
+ // first calculate the plot and axis box dimensions
997
+
998
+ $.each(axes, function (_, axis) {
999
+ axis.show = axis.options.show;
1000
+ if (axis.show == null)
1001
+ axis.show = axis.used; // by default an axis is visible if it's got data
1002
+
1003
+ axis.reserveSpace = axis.show || axis.options.reserveSpace;
1004
+
1005
+ setRange(axis);
1006
+ });
1007
+
1008
+ allocatedAxes = $.grep(axes, function (axis) { return axis.reserveSpace; });
1009
+
1010
+ plotOffset.left = plotOffset.right = plotOffset.top = plotOffset.bottom = 0;
1011
+ if (options.grid.show) {
1012
+ $.each(allocatedAxes, function (_, axis) {
1013
+ // make the ticks
1014
+ setupTickGeneration(axis);
1015
+ setTicks(axis);
1016
+ snapRangeToTicks(axis, axis.ticks);
1017
+
1018
+ // find labelWidth/Height for axis
1019
+ measureTickLabels(axis);
1020
+ });
1021
+
1022
+ // with all dimensions in house, we can compute the
1023
+ // axis boxes, start from the outside (reverse order)
1024
+ for (i = allocatedAxes.length - 1; i >= 0; --i)
1025
+ allocateAxisBoxFirstPhase(allocatedAxes[i]);
1026
+
1027
+ // make sure we've got enough space for things that
1028
+ // might stick out
1029
+ var minMargin = options.grid.minBorderMargin;
1030
+ if (minMargin == null) {
1031
+ minMargin = 0;
1032
+ for (i = 0; i < series.length; ++i)
1033
+ minMargin = Math.max(minMargin, series[i].points.radius + series[i].points.lineWidth/2);
1034
+ }
1035
+
1036
+ for (var a in plotOffset) {
1037
+ plotOffset[a] += options.grid.borderWidth;
1038
+ plotOffset[a] = Math.max(minMargin, plotOffset[a]);
1039
+ }
1040
+ }
1041
+
1042
+ plotWidth = canvasWidth - plotOffset.left - plotOffset.right;
1043
+ plotHeight = canvasHeight - plotOffset.bottom - plotOffset.top;
1044
+
1045
+ // now we got the proper plotWidth/Height, we can compute the scaling
1046
+ $.each(axes, function (_, axis) {
1047
+ setTransformationHelpers(axis);
1048
+ });
1049
+
1050
+ if (options.grid.show) {
1051
+ $.each(allocatedAxes, function (_, axis) {
1052
+ allocateAxisBoxSecondPhase(axis);
1053
+ });
1054
+
1055
+ insertAxisLabels();
1056
+ }
1057
+
1058
+ insertLegend();
1059
+ }
1060
+
1061
+ function setRange(axis) {
1062
+ var opts = axis.options,
1063
+ min = +(opts.min != null ? opts.min : axis.datamin),
1064
+ max = +(opts.max != null ? opts.max : axis.datamax),
1065
+ delta = max - min;
1066
+
1067
+ if (delta == 0.0) {
1068
+ // degenerate case
1069
+ var widen = max == 0 ? 1 : 0.01;
1070
+
1071
+ if (opts.min == null)
1072
+ min -= widen;
1073
+ // always widen max if we couldn't widen min to ensure we
1074
+ // don't fall into min == max which doesn't work
1075
+ if (opts.max == null || opts.min != null)
1076
+ max += widen;
1077
+ }
1078
+ else {
1079
+ // consider autoscaling
1080
+ var margin = opts.autoscaleMargin;
1081
+ if (margin != null) {
1082
+ if (opts.min == null) {
1083
+ min -= delta * margin;
1084
+ // make sure we don't go below zero if all values
1085
+ // are positive
1086
+ if (min < 0 && axis.datamin != null && axis.datamin >= 0)
1087
+ min = 0;
1088
+ }
1089
+ if (opts.max == null) {
1090
+ max += delta * margin;
1091
+ if (max > 0 && axis.datamax != null && axis.datamax <= 0)
1092
+ max = 0;
1093
+ }
1094
+ }
1095
+ }
1096
+ axis.min = min;
1097
+ axis.max = max;
1098
+ }
1099
+
1100
+ function setupTickGeneration(axis) {
1101
+ var opts = axis.options;
1102
+
1103
+ // estimate number of ticks
1104
+ var noTicks;
1105
+ if (typeof opts.ticks == "number" && opts.ticks > 0)
1106
+ noTicks = opts.ticks;
1107
+ else
1108
+ // heuristic based on the model a*sqrt(x) fitted to
1109
+ // some data points that seemed reasonable
1110
+ noTicks = 0.3 * Math.sqrt(axis.direction == "x" ? canvasWidth : canvasHeight);
1111
+
1112
+ var delta = (axis.max - axis.min) / noTicks,
1113
+ size, generator, unit, formatter, i, magn, norm;
1114
+
1115
+ if (opts.mode == "time") {
1116
+ // pretty handling of time
1117
+
1118
+ // map of app. size of time units in milliseconds
1119
+ var timeUnitSize = {
1120
+ "second": 1000,
1121
+ "minute": 60 * 1000,
1122
+ "hour": 60 * 60 * 1000,
1123
+ "day": 24 * 60 * 60 * 1000,
1124
+ "month": 30 * 24 * 60 * 60 * 1000,
1125
+ "year": 365.2425 * 24 * 60 * 60 * 1000
1126
+ };
1127
+
1128
+
1129
+ // the allowed tick sizes, after 1 year we use
1130
+ // an integer algorithm
1131
+ var spec = [
1132
+ [1, "second"], [2, "second"], [5, "second"], [10, "second"],
1133
+ [30, "second"],
1134
+ [1, "minute"], [2, "minute"], [5, "minute"], [10, "minute"],
1135
+ [30, "minute"],
1136
+ [1, "hour"], [2, "hour"], [4, "hour"],
1137
+ [8, "hour"], [12, "hour"],
1138
+ [1, "day"], [2, "day"], [3, "day"],
1139
+ [0.25, "month"], [0.5, "month"], [1, "month"],
1140
+ [2, "month"], [3, "month"], [6, "month"],
1141
+ [1, "year"]
1142
+ ];
1143
+
1144
+ var minSize = 0;
1145
+ if (opts.minTickSize != null) {
1146
+ if (typeof opts.tickSize == "number")
1147
+ minSize = opts.tickSize;
1148
+ else
1149
+ minSize = opts.minTickSize[0] * timeUnitSize[opts.minTickSize[1]];
1150
+ }
1151
+
1152
+ for (var i = 0; i < spec.length - 1; ++i)
1153
+ if (delta < (spec[i][0] * timeUnitSize[spec[i][1]]
1154
+ + spec[i + 1][0] * timeUnitSize[spec[i + 1][1]]) / 2
1155
+ && spec[i][0] * timeUnitSize[spec[i][1]] >= minSize)
1156
+ break;
1157
+ size = spec[i][0];
1158
+ unit = spec[i][1];
1159
+
1160
+ // special-case the possibility of several years
1161
+ if (unit == "year") {
1162
+ magn = Math.pow(10, Math.floor(Math.log(delta / timeUnitSize.year) / Math.LN10));
1163
+ norm = (delta / timeUnitSize.year) / magn;
1164
+ if (norm < 1.5)
1165
+ size = 1;
1166
+ else if (norm < 3)
1167
+ size = 2;
1168
+ else if (norm < 7.5)
1169
+ size = 5;
1170
+ else
1171
+ size = 10;
1172
+
1173
+ size *= magn;
1174
+ }
1175
+
1176
+ axis.tickSize = opts.tickSize || [size, unit];
1177
+
1178
+ generator = function(axis) {
1179
+ var ticks = [],
1180
+ tickSize = axis.tickSize[0], unit = axis.tickSize[1],
1181
+ d = new Date(axis.min);
1182
+
1183
+ var step = tickSize * timeUnitSize[unit];
1184
+
1185
+ if (unit == "second")
1186
+ d.setUTCSeconds(floorInBase(d.getUTCSeconds(), tickSize));
1187
+ if (unit == "minute")
1188
+ d.setUTCMinutes(floorInBase(d.getUTCMinutes(), tickSize));
1189
+ if (unit == "hour")
1190
+ d.setUTCHours(floorInBase(d.getUTCHours(), tickSize));
1191
+ if (unit == "month")
1192
+ d.setUTCMonth(floorInBase(d.getUTCMonth(), tickSize));
1193
+ if (unit == "year")
1194
+ d.setUTCFullYear(floorInBase(d.getUTCFullYear(), tickSize));
1195
+
1196
+ // reset smaller components
1197
+ d.setUTCMilliseconds(0);
1198
+ if (step >= timeUnitSize.minute)
1199
+ d.setUTCSeconds(0);
1200
+ if (step >= timeUnitSize.hour)
1201
+ d.setUTCMinutes(0);
1202
+ if (step >= timeUnitSize.day)
1203
+ d.setUTCHours(0);
1204
+ if (step >= timeUnitSize.day * 4)
1205
+ d.setUTCDate(1);
1206
+ if (step >= timeUnitSize.year)
1207
+ d.setUTCMonth(0);
1208
+
1209
+
1210
+ var carry = 0, v = Number.NaN, prev;
1211
+ do {
1212
+ prev = v;
1213
+ v = d.getTime();
1214
+ ticks.push(v);
1215
+ if (unit == "month") {
1216
+ if (tickSize < 1) {
1217
+ // a bit complicated - we'll divide the month
1218
+ // up but we need to take care of fractions
1219
+ // so we don't end up in the middle of a day
1220
+ d.setUTCDate(1);
1221
+ var start = d.getTime();
1222
+ d.setUTCMonth(d.getUTCMonth() + 1);
1223
+ var end = d.getTime();
1224
+ d.setTime(v + carry * timeUnitSize.hour + (end - start) * tickSize);
1225
+ carry = d.getUTCHours();
1226
+ d.setUTCHours(0);
1227
+ }
1228
+ else
1229
+ d.setUTCMonth(d.getUTCMonth() + tickSize);
1230
+ }
1231
+ else if (unit == "year") {
1232
+ d.setUTCFullYear(d.getUTCFullYear() + tickSize);
1233
+ }
1234
+ else
1235
+ d.setTime(v + step);
1236
+ } while (v < axis.max && v != prev);
1237
+
1238
+ return ticks;
1239
+ };
1240
+
1241
+ formatter = function (v, axis) {
1242
+ var d = new Date(v);
1243
+
1244
+ // first check global format
1245
+ if (opts.timeformat != null)
1246
+ return $.plot.formatDate(d, opts.timeformat, opts.monthNames);
1247
+
1248
+ var t = axis.tickSize[0] * timeUnitSize[axis.tickSize[1]];
1249
+ var span = axis.max - axis.min;
1250
+ var suffix = (opts.twelveHourClock) ? " %p" : "";
1251
+
1252
+ if (t < timeUnitSize.minute)
1253
+ fmt = "%h:%M:%S" + suffix;
1254
+ else if (t < timeUnitSize.day) {
1255
+ if (span < 2 * timeUnitSize.day)
1256
+ fmt = "%h:%M" + suffix;
1257
+ else
1258
+ fmt = "%b %d %h:%M" + suffix;
1259
+ }
1260
+ else if (t < timeUnitSize.month)
1261
+ fmt = "%b %d";
1262
+ else if (t < timeUnitSize.year) {
1263
+ if (span < timeUnitSize.year)
1264
+ fmt = "%b";
1265
+ else
1266
+ fmt = "%b %y";
1267
+ }
1268
+ else
1269
+ fmt = "%y";
1270
+
1271
+ return $.plot.formatDate(d, fmt, opts.monthNames);
1272
+ };
1273
+ }
1274
+ else {
1275
+ // pretty rounding of base-10 numbers
1276
+ var maxDec = opts.tickDecimals;
1277
+ var dec = -Math.floor(Math.log(delta) / Math.LN10);
1278
+ if (maxDec != null && dec > maxDec)
1279
+ dec = maxDec;
1280
+
1281
+ magn = Math.pow(10, -dec);
1282
+ norm = delta / magn; // norm is between 1.0 and 10.0
1283
+
1284
+ if (norm < 1.5)
1285
+ size = 1;
1286
+ else if (norm < 3) {
1287
+ size = 2;
1288
+ // special case for 2.5, requires an extra decimal
1289
+ if (norm > 2.25 && (maxDec == null || dec + 1 <= maxDec)) {
1290
+ size = 2.5;
1291
+ ++dec;
1292
+ }
1293
+ }
1294
+ else if (norm < 7.5)
1295
+ size = 5;
1296
+ else
1297
+ size = 10;
1298
+
1299
+ size *= magn;
1300
+
1301
+ if (opts.minTickSize != null && size < opts.minTickSize)
1302
+ size = opts.minTickSize;
1303
+
1304
+ axis.tickDecimals = Math.max(0, maxDec != null ? maxDec : dec);
1305
+ axis.tickSize = opts.tickSize || size;
1306
+
1307
+ generator = function (axis) {
1308
+ var ticks = [];
1309
+
1310
+ // spew out all possible ticks
1311
+ var start = floorInBase(axis.min, axis.tickSize),
1312
+ i = 0, v = Number.NaN, prev;
1313
+ do {
1314
+ prev = v;
1315
+ v = start + i * axis.tickSize;
1316
+ ticks.push(v);
1317
+ ++i;
1318
+ } while (v < axis.max && v != prev);
1319
+ return ticks;
1320
+ };
1321
+
1322
+ formatter = function (v, axis) {
1323
+ return v.toFixed(axis.tickDecimals);
1324
+ };
1325
+ }
1326
+
1327
+ if (opts.alignTicksWithAxis != null) {
1328
+ var otherAxis = (axis.direction == "x" ? xaxes : yaxes)[opts.alignTicksWithAxis - 1];
1329
+ if (otherAxis && otherAxis.used && otherAxis != axis) {
1330
+ // consider snapping min/max to outermost nice ticks
1331
+ var niceTicks = generator(axis);
1332
+ if (niceTicks.length > 0) {
1333
+ if (opts.min == null)
1334
+ axis.min = Math.min(axis.min, niceTicks[0]);
1335
+ if (opts.max == null && niceTicks.length > 1)
1336
+ axis.max = Math.max(axis.max, niceTicks[niceTicks.length - 1]);
1337
+ }
1338
+
1339
+ generator = function (axis) {
1340
+ // copy ticks, scaled to this axis
1341
+ var ticks = [], v, i;
1342
+ for (i = 0; i < otherAxis.ticks.length; ++i) {
1343
+ v = (otherAxis.ticks[i].v - otherAxis.min) / (otherAxis.max - otherAxis.min);
1344
+ v = axis.min + v * (axis.max - axis.min);
1345
+ ticks.push(v);
1346
+ }
1347
+ return ticks;
1348
+ };
1349
+
1350
+ // we might need an extra decimal since forced
1351
+ // ticks don't necessarily fit naturally
1352
+ if (axis.mode != "time" && opts.tickDecimals == null) {
1353
+ var extraDec = Math.max(0, -Math.floor(Math.log(delta) / Math.LN10) + 1),
1354
+ ts = generator(axis);
1355
+
1356
+ // only proceed if the tick interval rounded
1357
+ // with an extra decimal doesn't give us a
1358
+ // zero at end
1359
+ if (!(ts.length > 1 && /\..*0$/.test((ts[1] - ts[0]).toFixed(extraDec))))
1360
+ axis.tickDecimals = extraDec;
1361
+ }
1362
+ }
1363
+ }
1364
+
1365
+ axis.tickGenerator = generator;
1366
+ if ($.isFunction(opts.tickFormatter))
1367
+ axis.tickFormatter = function (v, axis) { return "" + opts.tickFormatter(v, axis); };
1368
+ else
1369
+ axis.tickFormatter = formatter;
1370
+ }
1371
+
1372
+ function setTicks(axis) {
1373
+ var oticks = axis.options.ticks, ticks = [];
1374
+ if (oticks == null || (typeof oticks == "number" && oticks > 0))
1375
+ ticks = axis.tickGenerator(axis);
1376
+ else if (oticks) {
1377
+ if ($.isFunction(oticks))
1378
+ // generate the ticks
1379
+ ticks = oticks({ min: axis.min, max: axis.max });
1380
+ else
1381
+ ticks = oticks;
1382
+ }
1383
+
1384
+ // clean up/labelify the supplied ticks, copy them over
1385
+ var i, v;
1386
+ axis.ticks = [];
1387
+ for (i = 0; i < ticks.length; ++i) {
1388
+ var label = null;
1389
+ var t = ticks[i];
1390
+ if (typeof t == "object") {
1391
+ v = +t[0];
1392
+ if (t.length > 1)
1393
+ label = t[1];
1394
+ }
1395
+ else
1396
+ v = +t;
1397
+ if (label == null)
1398
+ label = axis.tickFormatter(v, axis);
1399
+ if (!isNaN(v))
1400
+ axis.ticks.push({ v: v, label: label });
1401
+ }
1402
+ }
1403
+
1404
+ function snapRangeToTicks(axis, ticks) {
1405
+ if (axis.options.autoscaleMargin && ticks.length > 0) {
1406
+ // snap to ticks
1407
+ if (axis.options.min == null)
1408
+ axis.min = Math.min(axis.min, ticks[0].v);
1409
+ if (axis.options.max == null && ticks.length > 1)
1410
+ axis.max = Math.max(axis.max, ticks[ticks.length - 1].v);
1411
+ }
1412
+ }
1413
+
1414
+ function draw() {
1415
+ ctx.clearRect(0, 0, canvasWidth, canvasHeight);
1416
+
1417
+ var grid = options.grid;
1418
+
1419
+ // draw background, if any
1420
+ if (grid.show && grid.backgroundColor)
1421
+ drawBackground();
1422
+
1423
+ if (grid.show && !grid.aboveData)
1424
+ drawGrid();
1425
+
1426
+ for (var i = 0; i < series.length; ++i) {
1427
+ executeHooks(hooks.drawSeries, [ctx, series[i]]);
1428
+ drawSeries(series[i]);
1429
+ }
1430
+
1431
+ executeHooks(hooks.draw, [ctx]);
1432
+
1433
+ if (grid.show && grid.aboveData)
1434
+ drawGrid();
1435
+ }
1436
+
1437
+ function extractRange(ranges, coord) {
1438
+ var axis, from, to, key, axes = allAxes();
1439
+
1440
+ for (i = 0; i < axes.length; ++i) {
1441
+ axis = axes[i];
1442
+ if (axis.direction == coord) {
1443
+ key = coord + axis.n + "axis";
1444
+ if (!ranges[key] && axis.n == 1)
1445
+ key = coord + "axis"; // support x1axis as xaxis
1446
+ if (ranges[key]) {
1447
+ from = ranges[key].from;
1448
+ to = ranges[key].to;
1449
+ break;
1450
+ }
1451
+ }
1452
+ }
1453
+
1454
+ // backwards-compat stuff - to be removed in future
1455
+ if (!ranges[key]) {
1456
+ axis = coord == "x" ? xaxes[0] : yaxes[0];
1457
+ from = ranges[coord + "1"];
1458
+ to = ranges[coord + "2"];
1459
+ }
1460
+
1461
+ // auto-reverse as an added bonus
1462
+ if (from != null && to != null && from > to) {
1463
+ var tmp = from;
1464
+ from = to;
1465
+ to = tmp;
1466
+ }
1467
+
1468
+ return { from: from, to: to, axis: axis };
1469
+ }
1470
+
1471
+ function drawBackground() {
1472
+ ctx.save();
1473
+ ctx.translate(plotOffset.left, plotOffset.top);
1474
+
1475
+ ctx.fillStyle = getColorOrGradient(options.grid.backgroundColor, plotHeight, 0, "rgba(255, 255, 255, 0)");
1476
+ ctx.fillRect(0, 0, plotWidth, plotHeight);
1477
+ ctx.restore();
1478
+ }
1479
+
1480
+ function drawGrid() {
1481
+ var i;
1482
+
1483
+ ctx.save();
1484
+ ctx.translate(plotOffset.left, plotOffset.top);
1485
+
1486
+ // draw markings
1487
+ var markings = options.grid.markings;
1488
+ if (markings) {
1489
+ if ($.isFunction(markings)) {
1490
+ var axes = plot.getAxes();
1491
+ // xmin etc. is backwards compatibility, to be
1492
+ // removed in the future
1493
+ axes.xmin = axes.xaxis.min;
1494
+ axes.xmax = axes.xaxis.max;
1495
+ axes.ymin = axes.yaxis.min;
1496
+ axes.ymax = axes.yaxis.max;
1497
+
1498
+ markings = markings(axes);
1499
+ }
1500
+
1501
+ for (i = 0; i < markings.length; ++i) {
1502
+ var m = markings[i],
1503
+ xrange = extractRange(m, "x"),
1504
+ yrange = extractRange(m, "y");
1505
+
1506
+ // fill in missing
1507
+ if (xrange.from == null)
1508
+ xrange.from = xrange.axis.min;
1509
+ if (xrange.to == null)
1510
+ xrange.to = xrange.axis.max;
1511
+ if (yrange.from == null)
1512
+ yrange.from = yrange.axis.min;
1513
+ if (yrange.to == null)
1514
+ yrange.to = yrange.axis.max;
1515
+
1516
+ // clip
1517
+ if (xrange.to < xrange.axis.min || xrange.from > xrange.axis.max ||
1518
+ yrange.to < yrange.axis.min || yrange.from > yrange.axis.max)
1519
+ continue;
1520
+
1521
+ xrange.from = Math.max(xrange.from, xrange.axis.min);
1522
+ xrange.to = Math.min(xrange.to, xrange.axis.max);
1523
+ yrange.from = Math.max(yrange.from, yrange.axis.min);
1524
+ yrange.to = Math.min(yrange.to, yrange.axis.max);
1525
+
1526
+ if (xrange.from == xrange.to && yrange.from == yrange.to)
1527
+ continue;
1528
+
1529
+ // then draw
1530
+ xrange.from = xrange.axis.p2c(xrange.from);
1531
+ xrange.to = xrange.axis.p2c(xrange.to);
1532
+ yrange.from = yrange.axis.p2c(yrange.from);
1533
+ yrange.to = yrange.axis.p2c(yrange.to);
1534
+
1535
+ if (xrange.from == xrange.to || yrange.from == yrange.to) {
1536
+ // draw line
1537
+ ctx.beginPath();
1538
+ ctx.strokeStyle = m.color || options.grid.markingsColor;
1539
+ ctx.lineWidth = m.lineWidth || options.grid.markingsLineWidth;
1540
+ ctx.moveTo(xrange.from, yrange.from);
1541
+ ctx.lineTo(xrange.to, yrange.to);
1542
+ ctx.stroke();
1543
+ }
1544
+ else {
1545
+ // fill area
1546
+ ctx.fillStyle = m.color || options.grid.markingsColor;
1547
+ ctx.fillRect(xrange.from, yrange.to,
1548
+ xrange.to - xrange.from,
1549
+ yrange.from - yrange.to);
1550
+ }
1551
+ }
1552
+ }
1553
+
1554
+ // draw the ticks
1555
+ var axes = allAxes(), bw = options.grid.borderWidth;
1556
+
1557
+ for (var j = 0; j < axes.length; ++j) {
1558
+ var axis = axes[j], box = axis.box,
1559
+ t = axis.tickLength, x, y, xoff, yoff;
1560
+ if (!axis.show || axis.ticks.length == 0)
1561
+ continue
1562
+
1563
+ ctx.strokeStyle = axis.options.tickColor || $.color.parse(axis.options.color).scale('a', 0.22).toString();
1564
+ ctx.lineWidth = 1;
1565
+
1566
+ // find the edges
1567
+ if (axis.direction == "x") {
1568
+ x = 0;
1569
+ if (t == "full")
1570
+ y = (axis.position == "top" ? 0 : plotHeight);
1571
+ else
1572
+ y = box.top - plotOffset.top + (axis.position == "top" ? box.height : 0);
1573
+ }
1574
+ else {
1575
+ y = 0;
1576
+ if (t == "full")
1577
+ x = (axis.position == "left" ? 0 : plotWidth);
1578
+ else
1579
+ x = box.left - plotOffset.left + (axis.position == "left" ? box.width : 0);
1580
+ }
1581
+
1582
+ // draw tick bar
1583
+ if (!axis.innermost) {
1584
+ ctx.beginPath();
1585
+ xoff = yoff = 0;
1586
+ if (axis.direction == "x")
1587
+ xoff = plotWidth;
1588
+ else
1589
+ yoff = plotHeight;
1590
+
1591
+ if (ctx.lineWidth == 1) {
1592
+ x = Math.floor(x) + 0.5;
1593
+ y = Math.floor(y) + 0.5;
1594
+ }
1595
+
1596
+ ctx.moveTo(x, y);
1597
+ ctx.lineTo(x + xoff, y + yoff);
1598
+ ctx.stroke();
1599
+ }
1600
+
1601
+ // draw ticks
1602
+ ctx.beginPath();
1603
+ for (i = 0; i < axis.ticks.length; ++i) {
1604
+ var v = axis.ticks[i].v;
1605
+
1606
+ xoff = yoff = 0;
1607
+
1608
+ if (v < axis.min || v > axis.max
1609
+ // skip those lying on the axes if we got a border
1610
+ || (t == "full" && bw > 0
1611
+ && (v == axis.min || v == axis.max)))
1612
+ continue;
1613
+
1614
+ if (axis.direction == "x") {
1615
+ x = axis.p2c(v);
1616
+ yoff = t == "full" ? -plotHeight : t;
1617
+
1618
+ if (axis.position == "top")
1619
+ yoff = -yoff;
1620
+ }
1621
+ else {
1622
+ y = axis.p2c(v);
1623
+ xoff = t == "full" ? -plotWidth : t;
1624
+
1625
+ if (axis.position == "left")
1626
+ xoff = -xoff;
1627
+ }
1628
+
1629
+ if (ctx.lineWidth == 1) {
1630
+ if (axis.direction == "x")
1631
+ x = Math.floor(x) + 0.5;
1632
+ else
1633
+ y = Math.floor(y) + 0.5;
1634
+ }
1635
+
1636
+ ctx.moveTo(x, y);
1637
+ ctx.lineTo(x + xoff, y + yoff);
1638
+ }
1639
+
1640
+ ctx.stroke();
1641
+ }
1642
+
1643
+
1644
+ // draw border
1645
+ if (bw) {
1646
+ ctx.lineWidth = bw;
1647
+ ctx.strokeStyle = options.grid.borderColor;
1648
+ ctx.strokeRect(-bw/2, -bw/2, plotWidth + bw, plotHeight + bw);
1649
+ }
1650
+
1651
+ ctx.restore();
1652
+ }
1653
+
1654
+ function insertAxisLabels() {
1655
+ placeholder.find(".tickLabels").remove();
1656
+
1657
+ var html = ['<div class="tickLabels" style="font-size:smaller">'];
1658
+
1659
+ var axes = allAxes();
1660
+ for (var j = 0; j < axes.length; ++j) {
1661
+ var axis = axes[j], box = axis.box;
1662
+ if (!axis.show)
1663
+ continue;
1664
+ //debug: html.push('<div style="position:absolute;opacity:0.10;background-color:red;left:' + box.left + 'px;top:' + box.top + 'px;width:' + box.width + 'px;height:' + box.height + 'px"></div>')
1665
+ html.push('<div class="' + axis.direction + 'Axis ' + axis.direction + axis.n + 'Axis" style="color:' + axis.options.color + '">');
1666
+ for (var i = 0; i < axis.ticks.length; ++i) {
1667
+ var tick = axis.ticks[i];
1668
+ if (!tick.label || tick.v < axis.min || tick.v > axis.max)
1669
+ continue;
1670
+
1671
+ var pos = {}, align;
1672
+
1673
+ if (axis.direction == "x") {
1674
+ align = "center";
1675
+ pos.left = Math.round(plotOffset.left + axis.p2c(tick.v) - axis.labelWidth/2);
1676
+ if (axis.position == "bottom")
1677
+ pos.top = box.top + box.padding;
1678
+ else
1679
+ pos.bottom = canvasHeight - (box.top + box.height - box.padding);
1680
+ }
1681
+ else {
1682
+ pos.top = Math.round(plotOffset.top + axis.p2c(tick.v) - axis.labelHeight/2);
1683
+ if (axis.position == "left") {
1684
+ pos.right = canvasWidth - (box.left + box.width - box.padding)
1685
+ align = "right";
1686
+ }
1687
+ else {
1688
+ pos.left = box.left + box.padding;
1689
+ align = "left";
1690
+ }
1691
+ }
1692
+
1693
+ pos.width = axis.labelWidth;
1694
+
1695
+ var style = ["position:absolute", "text-align:" + align ];
1696
+ for (var a in pos)
1697
+ style.push(a + ":" + pos[a] + "px")
1698
+
1699
+ html.push('<div class="tickLabel" style="' + style.join(';') + '">' + tick.label + '</div>');
1700
+ }
1701
+ html.push('</div>');
1702
+ }
1703
+
1704
+ html.push('</div>');
1705
+
1706
+ placeholder.append(html.join(""));
1707
+ }
1708
+
1709
+ function drawSeries(series) {
1710
+ if (series.lines.show)
1711
+ drawSeriesLines(series);
1712
+ if (series.bars.show)
1713
+ drawSeriesBars(series);
1714
+ if (series.points.show)
1715
+ drawSeriesPoints(series);
1716
+ }
1717
+
1718
+ function drawSeriesLines(series) {
1719
+ function plotLine(datapoints, xoffset, yoffset, axisx, axisy) {
1720
+ var points = datapoints.points,
1721
+ ps = datapoints.pointsize,
1722
+ prevx = null, prevy = null;
1723
+
1724
+ ctx.beginPath();
1725
+ for (var i = ps; i < points.length; i += ps) {
1726
+ var x1 = points[i - ps], y1 = points[i - ps + 1],
1727
+ x2 = points[i], y2 = points[i + 1];
1728
+
1729
+ if (x1 == null || x2 == null)
1730
+ continue;
1731
+
1732
+ // clip with ymin
1733
+ if (y1 <= y2 && y1 < axisy.min) {
1734
+ if (y2 < axisy.min)
1735
+ continue; // line segment is outside
1736
+ // compute new intersection point
1737
+ x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
1738
+ y1 = axisy.min;
1739
+ }
1740
+ else if (y2 <= y1 && y2 < axisy.min) {
1741
+ if (y1 < axisy.min)
1742
+ continue;
1743
+ x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
1744
+ y2 = axisy.min;
1745
+ }
1746
+
1747
+ // clip with ymax
1748
+ if (y1 >= y2 && y1 > axisy.max) {
1749
+ if (y2 > axisy.max)
1750
+ continue;
1751
+ x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
1752
+ y1 = axisy.max;
1753
+ }
1754
+ else if (y2 >= y1 && y2 > axisy.max) {
1755
+ if (y1 > axisy.max)
1756
+ continue;
1757
+ x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
1758
+ y2 = axisy.max;
1759
+ }
1760
+
1761
+ // clip with xmin
1762
+ if (x1 <= x2 && x1 < axisx.min) {
1763
+ if (x2 < axisx.min)
1764
+ continue;
1765
+ y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
1766
+ x1 = axisx.min;
1767
+ }
1768
+ else if (x2 <= x1 && x2 < axisx.min) {
1769
+ if (x1 < axisx.min)
1770
+ continue;
1771
+ y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
1772
+ x2 = axisx.min;
1773
+ }
1774
+
1775
+ // clip with xmax
1776
+ if (x1 >= x2 && x1 > axisx.max) {
1777
+ if (x2 > axisx.max)
1778
+ continue;
1779
+ y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
1780
+ x1 = axisx.max;
1781
+ }
1782
+ else if (x2 >= x1 && x2 > axisx.max) {
1783
+ if (x1 > axisx.max)
1784
+ continue;
1785
+ y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
1786
+ x2 = axisx.max;
1787
+ }
1788
+
1789
+ if (x1 != prevx || y1 != prevy)
1790
+ ctx.moveTo(axisx.p2c(x1) + xoffset, axisy.p2c(y1) + yoffset);
1791
+
1792
+ prevx = x2;
1793
+ prevy = y2;
1794
+ ctx.lineTo(axisx.p2c(x2) + xoffset, axisy.p2c(y2) + yoffset);
1795
+ }
1796
+ ctx.stroke();
1797
+ }
1798
+
1799
+ function plotLineArea(datapoints, axisx, axisy) {
1800
+ var points = datapoints.points,
1801
+ ps = datapoints.pointsize,
1802
+ bottom = Math.min(Math.max(0, axisy.min), axisy.max),
1803
+ i = 0, top, areaOpen = false,
1804
+ ypos = 1, segmentStart = 0, segmentEnd = 0;
1805
+
1806
+ // we process each segment in two turns, first forward
1807
+ // direction to sketch out top, then once we hit the
1808
+ // end we go backwards to sketch the bottom
1809
+ while (true) {
1810
+ if (ps > 0 && i > points.length + ps)
1811
+ break;
1812
+
1813
+ i += ps; // ps is negative if going backwards
1814
+
1815
+ var x1 = points[i - ps],
1816
+ y1 = points[i - ps + ypos],
1817
+ x2 = points[i], y2 = points[i + ypos];
1818
+
1819
+ if (areaOpen) {
1820
+ if (ps > 0 && x1 != null && x2 == null) {
1821
+ // at turning point
1822
+ segmentEnd = i;
1823
+ ps = -ps;
1824
+ ypos = 2;
1825
+ continue;
1826
+ }
1827
+
1828
+ if (ps < 0 && i == segmentStart + ps) {
1829
+ // done with the reverse sweep
1830
+ ctx.fill();
1831
+ areaOpen = false;
1832
+ ps = -ps;
1833
+ ypos = 1;
1834
+ i = segmentStart = segmentEnd + ps;
1835
+ continue;
1836
+ }
1837
+ }
1838
+
1839
+ if (x1 == null || x2 == null)
1840
+ continue;
1841
+
1842
+ // clip x values
1843
+
1844
+ // clip with xmin
1845
+ if (x1 <= x2 && x1 < axisx.min) {
1846
+ if (x2 < axisx.min)
1847
+ continue;
1848
+ y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
1849
+ x1 = axisx.min;
1850
+ }
1851
+ else if (x2 <= x1 && x2 < axisx.min) {
1852
+ if (x1 < axisx.min)
1853
+ continue;
1854
+ y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
1855
+ x2 = axisx.min;
1856
+ }
1857
+
1858
+ // clip with xmax
1859
+ if (x1 >= x2 && x1 > axisx.max) {
1860
+ if (x2 > axisx.max)
1861
+ continue;
1862
+ y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
1863
+ x1 = axisx.max;
1864
+ }
1865
+ else if (x2 >= x1 && x2 > axisx.max) {
1866
+ if (x1 > axisx.max)
1867
+ continue;
1868
+ y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
1869
+ x2 = axisx.max;
1870
+ }
1871
+
1872
+ if (!areaOpen) {
1873
+ // open area
1874
+ ctx.beginPath();
1875
+ ctx.moveTo(axisx.p2c(x1), axisy.p2c(bottom));
1876
+ areaOpen = true;
1877
+ }
1878
+
1879
+ // now first check the case where both is outside
1880
+ if (y1 >= axisy.max && y2 >= axisy.max) {
1881
+ ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.max));
1882
+ ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.max));
1883
+ continue;
1884
+ }
1885
+ else if (y1 <= axisy.min && y2 <= axisy.min) {
1886
+ ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.min));
1887
+ ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.min));
1888
+ continue;
1889
+ }
1890
+
1891
+ // else it's a bit more complicated, there might
1892
+ // be a flat maxed out rectangle first, then a
1893
+ // triangular cutout or reverse; to find these
1894
+ // keep track of the current x values
1895
+ var x1old = x1, x2old = x2;
1896
+
1897
+ // clip the y values, without shortcutting, we
1898
+ // go through all cases in turn
1899
+
1900
+ // clip with ymin
1901
+ if (y1 <= y2 && y1 < axisy.min && y2 >= axisy.min) {
1902
+ x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
1903
+ y1 = axisy.min;
1904
+ }
1905
+ else if (y2 <= y1 && y2 < axisy.min && y1 >= axisy.min) {
1906
+ x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
1907
+ y2 = axisy.min;
1908
+ }
1909
+
1910
+ // clip with ymax
1911
+ if (y1 >= y2 && y1 > axisy.max && y2 <= axisy.max) {
1912
+ x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
1913
+ y1 = axisy.max;
1914
+ }
1915
+ else if (y2 >= y1 && y2 > axisy.max && y1 <= axisy.max) {
1916
+ x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
1917
+ y2 = axisy.max;
1918
+ }
1919
+
1920
+ // if the x value was changed we got a rectangle
1921
+ // to fill
1922
+ if (x1 != x1old) {
1923
+ ctx.lineTo(axisx.p2c(x1old), axisy.p2c(y1));
1924
+ // it goes to (x1, y1), but we fill that below
1925
+ }
1926
+
1927
+ // fill triangular section, this sometimes result
1928
+ // in redundant points if (x1, y1) hasn't changed
1929
+ // from previous line to, but we just ignore that
1930
+ ctx.lineTo(axisx.p2c(x1), axisy.p2c(y1));
1931
+ ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2));
1932
+
1933
+ // fill the other rectangle if it's there
1934
+ if (x2 != x2old) {
1935
+ ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2));
1936
+ ctx.lineTo(axisx.p2c(x2old), axisy.p2c(y2));
1937
+ }
1938
+ }
1939
+ }
1940
+
1941
+ ctx.save();
1942
+ ctx.translate(plotOffset.left, plotOffset.top);
1943
+ ctx.lineJoin = "round";
1944
+
1945
+ var lw = series.lines.lineWidth,
1946
+ sw = series.shadowSize;
1947
+ // FIXME: consider another form of shadow when filling is turned on
1948
+ if (lw > 0 && sw > 0) {
1949
+ // draw shadow as a thick and thin line with transparency
1950
+ ctx.lineWidth = sw;
1951
+ ctx.strokeStyle = "rgba(0,0,0,0.1)";
1952
+ // position shadow at angle from the mid of line
1953
+ var angle = Math.PI/18;
1954
+ plotLine(series.datapoints, Math.sin(angle) * (lw/2 + sw/2), Math.cos(angle) * (lw/2 + sw/2), series.xaxis, series.yaxis);
1955
+ ctx.lineWidth = sw/2;
1956
+ plotLine(series.datapoints, Math.sin(angle) * (lw/2 + sw/4), Math.cos(angle) * (lw/2 + sw/4), series.xaxis, series.yaxis);
1957
+ }
1958
+
1959
+ ctx.lineWidth = lw;
1960
+ ctx.strokeStyle = series.color;
1961
+ var fillStyle = getFillStyle(series.lines, series.color, 0, plotHeight);
1962
+ if (fillStyle) {
1963
+ ctx.fillStyle = fillStyle;
1964
+ plotLineArea(series.datapoints, series.xaxis, series.yaxis);
1965
+ }
1966
+
1967
+ if (lw > 0)
1968
+ plotLine(series.datapoints, 0, 0, series.xaxis, series.yaxis);
1969
+ ctx.restore();
1970
+ }
1971
+
1972
+ function drawSeriesPoints(series) {
1973
+ function plotPoints(datapoints, radius, fillStyle, offset, shadow, axisx, axisy, symbol) {
1974
+ var points = datapoints.points, ps = datapoints.pointsize;
1975
+
1976
+ for (var i = 0; i < points.length; i += ps) {
1977
+ var x = points[i], y = points[i + 1];
1978
+ if (x == null || x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max)
1979
+ continue;
1980
+
1981
+ ctx.beginPath();
1982
+ x = axisx.p2c(x);
1983
+ y = axisy.p2c(y) + offset;
1984
+ if (symbol == "circle")
1985
+ ctx.arc(x, y, radius, 0, shadow ? Math.PI : Math.PI * 2, false);
1986
+ else
1987
+ symbol(ctx, x, y, radius, shadow);
1988
+ ctx.closePath();
1989
+
1990
+ if (fillStyle) {
1991
+ ctx.fillStyle = fillStyle;
1992
+ ctx.fill();
1993
+ }
1994
+ ctx.stroke();
1995
+ }
1996
+ }
1997
+
1998
+ ctx.save();
1999
+ ctx.translate(plotOffset.left, plotOffset.top);
2000
+
2001
+ var lw = series.points.lineWidth,
2002
+ sw = series.shadowSize,
2003
+ radius = series.points.radius,
2004
+ symbol = series.points.symbol;
2005
+ if (lw > 0 && sw > 0) {
2006
+ // draw shadow in two steps
2007
+ var w = sw / 2;
2008
+ ctx.lineWidth = w;
2009
+ ctx.strokeStyle = "rgba(0,0,0,0.1)";
2010
+ plotPoints(series.datapoints, radius, null, w + w/2, true,
2011
+ series.xaxis, series.yaxis, symbol);
2012
+
2013
+ ctx.strokeStyle = "rgba(0,0,0,0.2)";
2014
+ plotPoints(series.datapoints, radius, null, w/2, true,
2015
+ series.xaxis, series.yaxis, symbol);
2016
+ }
2017
+
2018
+ ctx.lineWidth = lw;
2019
+ ctx.strokeStyle = series.color;
2020
+ plotPoints(series.datapoints, radius,
2021
+ getFillStyle(series.points, series.color), 0, false,
2022
+ series.xaxis, series.yaxis, symbol);
2023
+ ctx.restore();
2024
+ }
2025
+
2026
+ function drawBar(x, y, b, barLeft, barRight, offset, fillStyleCallback, axisx, axisy, c, horizontal, lineWidth) {
2027
+ var left, right, bottom, top,
2028
+ drawLeft, drawRight, drawTop, drawBottom,
2029
+ tmp;
2030
+
2031
+ // in horizontal mode, we start the bar from the left
2032
+ // instead of from the bottom so it appears to be
2033
+ // horizontal rather than vertical
2034
+ if (horizontal) {
2035
+ drawBottom = drawRight = drawTop = true;
2036
+ drawLeft = false;
2037
+ left = b;
2038
+ right = x;
2039
+ top = y + barLeft;
2040
+ bottom = y + barRight;
2041
+
2042
+ // account for negative bars
2043
+ if (right < left) {
2044
+ tmp = right;
2045
+ right = left;
2046
+ left = tmp;
2047
+ drawLeft = true;
2048
+ drawRight = false;
2049
+ }
2050
+ }
2051
+ else {
2052
+ drawLeft = drawRight = drawTop = true;
2053
+ drawBottom = false;
2054
+ left = x + barLeft;
2055
+ right = x + barRight;
2056
+ bottom = b;
2057
+ top = y;
2058
+
2059
+ // account for negative bars
2060
+ if (top < bottom) {
2061
+ tmp = top;
2062
+ top = bottom;
2063
+ bottom = tmp;
2064
+ drawBottom = true;
2065
+ drawTop = false;
2066
+ }
2067
+ }
2068
+
2069
+ // clip
2070
+ if (right < axisx.min || left > axisx.max ||
2071
+ top < axisy.min || bottom > axisy.max)
2072
+ return;
2073
+
2074
+ if (left < axisx.min) {
2075
+ left = axisx.min;
2076
+ drawLeft = false;
2077
+ }
2078
+
2079
+ if (right > axisx.max) {
2080
+ right = axisx.max;
2081
+ drawRight = false;
2082
+ }
2083
+
2084
+ if (bottom < axisy.min) {
2085
+ bottom = axisy.min;
2086
+ drawBottom = false;
2087
+ }
2088
+
2089
+ if (top > axisy.max) {
2090
+ top = axisy.max;
2091
+ drawTop = false;
2092
+ }
2093
+
2094
+ left = axisx.p2c(left);
2095
+ bottom = axisy.p2c(bottom);
2096
+ right = axisx.p2c(right);
2097
+ top = axisy.p2c(top);
2098
+
2099
+ // fill the bar
2100
+ if (fillStyleCallback) {
2101
+ c.beginPath();
2102
+ c.moveTo(left, bottom);
2103
+ c.lineTo(left, top);
2104
+ c.lineTo(right, top);
2105
+ c.lineTo(right, bottom);
2106
+ c.fillStyle = fillStyleCallback(bottom, top);
2107
+ c.fill();
2108
+ }
2109
+
2110
+ // draw outline
2111
+ if (lineWidth > 0 && (drawLeft || drawRight || drawTop || drawBottom)) {
2112
+ c.beginPath();
2113
+
2114
+ // FIXME: inline moveTo is buggy with excanvas
2115
+ c.moveTo(left, bottom + offset);
2116
+ if (drawLeft)
2117
+ c.lineTo(left, top + offset);
2118
+ else
2119
+ c.moveTo(left, top + offset);
2120
+ if (drawTop)
2121
+ c.lineTo(right, top + offset);
2122
+ else
2123
+ c.moveTo(right, top + offset);
2124
+ if (drawRight)
2125
+ c.lineTo(right, bottom + offset);
2126
+ else
2127
+ c.moveTo(right, bottom + offset);
2128
+ if (drawBottom)
2129
+ c.lineTo(left, bottom + offset);
2130
+ else
2131
+ c.moveTo(left, bottom + offset);
2132
+ c.stroke();
2133
+ }
2134
+ }
2135
+
2136
+ function drawSeriesBars(series) {
2137
+ function plotBars(datapoints, barLeft, barRight, offset, fillStyleCallback, axisx, axisy) {
2138
+ var points = datapoints.points, ps = datapoints.pointsize;
2139
+
2140
+ for (var i = 0; i < points.length; i += ps) {
2141
+ if (points[i] == null)
2142
+ continue;
2143
+ drawBar(points[i], points[i + 1], points[i + 2], barLeft, barRight, offset, fillStyleCallback, axisx, axisy, ctx, series.bars.horizontal, series.bars.lineWidth);
2144
+ }
2145
+ }
2146
+
2147
+ ctx.save();
2148
+ ctx.translate(plotOffset.left, plotOffset.top);
2149
+
2150
+ // FIXME: figure out a way to add shadows (for instance along the right edge)
2151
+ ctx.lineWidth = series.bars.lineWidth;
2152
+ ctx.strokeStyle = series.color;
2153
+ var barLeft = series.bars.align == "left" ? 0 : -series.bars.barWidth/2;
2154
+ var fillStyleCallback = series.bars.fill ? function (bottom, top) { return getFillStyle(series.bars, series.color, bottom, top); } : null;
2155
+ plotBars(series.datapoints, barLeft, barLeft + series.bars.barWidth, 0, fillStyleCallback, series.xaxis, series.yaxis);
2156
+ ctx.restore();
2157
+ }
2158
+
2159
+ function getFillStyle(filloptions, seriesColor, bottom, top) {
2160
+ var fill = filloptions.fill;
2161
+ if (!fill)
2162
+ return null;
2163
+
2164
+ if (filloptions.fillColor)
2165
+ return getColorOrGradient(filloptions.fillColor, bottom, top, seriesColor);
2166
+
2167
+ var c = $.color.parse(seriesColor);
2168
+ c.a = typeof fill == "number" ? fill : 0.1;
2169
+ c.normalize();
2170
+ return c.toString();
2171
+ }
2172
+
2173
+ function insertLegend() {
2174
+ placeholder.find(".legend").remove();
2175
+
2176
+ if (!options.legend.show)
2177
+ return;
2178
+
2179
+ var fragments = [], rowStarted = false,
2180
+ lf = options.legend.labelFormatter, s, label;
2181
+ for (var i = 0; i < series.length; ++i) {
2182
+ s = series[i];
2183
+ label = s.label;
2184
+ if (!label)
2185
+ continue;
2186
+
2187
+ if (i % options.legend.noColumns == 0) {
2188
+ if (rowStarted)
2189
+ fragments.push('</tr>');
2190
+ fragments.push('<tr>');
2191
+ rowStarted = true;
2192
+ }
2193
+
2194
+ if (lf)
2195
+ label = lf(label, s);
2196
+
2197
+ fragments.push(
2198
+ '<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>' +
2199
+ '<td class="legendLabel">' + label + '</td>');
2200
+ }
2201
+ if (rowStarted)
2202
+ fragments.push('</tr>');
2203
+
2204
+ if (fragments.length == 0)
2205
+ return;
2206
+
2207
+ var table = '<table style="font-size:11px;color:' + options.grid.color + '">' + fragments.join("") + '</table>';
2208
+ if (options.legend.container != null)
2209
+ $(options.legend.container).html(table);
2210
+ else {
2211
+ var pos = "",
2212
+ p = options.legend.position,
2213
+ m = options.legend.margin;
2214
+ if (m[0] == null)
2215
+ m = [m, m];
2216
+ if (p.charAt(0) == "n")
2217
+ pos += 'top:' + (m[1] + plotOffset.top) + 'px;';
2218
+ else if (p.charAt(0) == "s")
2219
+ pos += 'bottom:' + (m[1] + plotOffset.bottom) + 'px;';
2220
+ if (p.charAt(1) == "e")
2221
+ pos += 'right:' + (m[0] + plotOffset.right) + 'px;';
2222
+ else if (p.charAt(1) == "w")
2223
+ pos += 'left:' + (m[0] + plotOffset.left) + 'px;';
2224
+ var legend = $('<div class="legend">' + table.replace('style="', 'style="position:absolute;' + pos +'margin:-27px 0 0 -5px;') + '</div>').appendTo(placeholder);
2225
+ if (options.legend.backgroundOpacity != 0.0) {
2226
+ // put in the transparent background
2227
+ // separately to avoid blended labels and
2228
+ // label boxes
2229
+ var c = options.legend.backgroundColor;
2230
+ if (c == null) {
2231
+ c = options.grid.backgroundColor;
2232
+ if (c && typeof c == "string")
2233
+ c = $.color.parse(c);
2234
+ else
2235
+ c = $.color.extract(legend, 'background-color');
2236
+ c.a = 1;
2237
+ c = c.toString();
2238
+ }
2239
+ var div = legend.children();
2240
+ $('<div style="position:absolute;width:' + div.width() + 'px;height:' + div.height() + 'px;' + pos +'background-color:' + c + ';"> </div>').prependTo(legend).css('opacity', options.legend.backgroundOpacity);
2241
+ }
2242
+ }
2243
+ }
2244
+
2245
+
2246
+ // interactive features
2247
+
2248
+ var highlights = [],
2249
+ redrawTimeout = null;
2250
+
2251
+ // returns the data item the mouse is over, or null if none is found
2252
+ function findNearbyItem(mouseX, mouseY, seriesFilter) {
2253
+ var maxDistance = options.grid.mouseActiveRadius,
2254
+ smallestDistance = maxDistance * maxDistance + 1,
2255
+ item = null, foundPoint = false, i, j;
2256
+
2257
+ for (i = series.length - 1; i >= 0; --i) {
2258
+ if (!seriesFilter(series[i]))
2259
+ continue;
2260
+
2261
+ var s = series[i],
2262
+ axisx = s.xaxis,
2263
+ axisy = s.yaxis,
2264
+ points = s.datapoints.points,
2265
+ ps = s.datapoints.pointsize,
2266
+ mx = axisx.c2p(mouseX), // precompute some stuff to make the loop faster
2267
+ my = axisy.c2p(mouseY),
2268
+ maxx = maxDistance / axisx.scale,
2269
+ maxy = maxDistance / axisy.scale;
2270
+
2271
+ // with inverse transforms, we can't use the maxx/maxy
2272
+ // optimization, sadly
2273
+ if (axisx.options.inverseTransform)
2274
+ maxx = Number.MAX_VALUE;
2275
+ if (axisy.options.inverseTransform)
2276
+ maxy = Number.MAX_VALUE;
2277
+
2278
+ if (s.lines.show || s.points.show) {
2279
+ for (j = 0; j < points.length; j += ps) {
2280
+ var x = points[j], y = points[j + 1];
2281
+ if (x == null)
2282
+ continue;
2283
+
2284
+ // For points and lines, the cursor must be within a
2285
+ // certain distance to the data point
2286
+ if (x - mx > maxx || x - mx < -maxx ||
2287
+ y - my > maxy || y - my < -maxy)
2288
+ continue;
2289
+
2290
+ // We have to calculate distances in pixels, not in
2291
+ // data units, because the scales of the axes may be different
2292
+ var dx = Math.abs(axisx.p2c(x) - mouseX),
2293
+ dy = Math.abs(axisy.p2c(y) - mouseY),
2294
+ dist = dx * dx + dy * dy; // we save the sqrt
2295
+
2296
+ // use <= to ensure last point takes precedence
2297
+ // (last generally means on top of)
2298
+ if (dist < smallestDistance) {
2299
+ smallestDistance = dist;
2300
+ item = [i, j / ps];
2301
+ }
2302
+ }
2303
+ }
2304
+
2305
+ if (s.bars.show && !item) { // no other point can be nearby
2306
+ var barLeft = s.bars.align == "left" ? 0 : -s.bars.barWidth/2,
2307
+ barRight = barLeft + s.bars.barWidth;
2308
+
2309
+ for (j = 0; j < points.length; j += ps) {
2310
+ var x = points[j], y = points[j + 1], b = points[j + 2];
2311
+ if (x == null)
2312
+ continue;
2313
+
2314
+ // for a bar graph, the cursor must be inside the bar
2315
+ if (series[i].bars.horizontal ?
2316
+ (mx <= Math.max(b, x) && mx >= Math.min(b, x) &&
2317
+ my >= y + barLeft && my <= y + barRight) :
2318
+ (mx >= x + barLeft && mx <= x + barRight &&
2319
+ my >= Math.min(b, y) && my <= Math.max(b, y)))
2320
+ item = [i, j / ps];
2321
+ }
2322
+ }
2323
+ }
2324
+
2325
+ if (item) {
2326
+ i = item[0];
2327
+ j = item[1];
2328
+ ps = series[i].datapoints.pointsize;
2329
+
2330
+ return { datapoint: series[i].datapoints.points.slice(j * ps, (j + 1) * ps),
2331
+ dataIndex: j,
2332
+ series: series[i],
2333
+ seriesIndex: i };
2334
+ }
2335
+
2336
+ return null;
2337
+ }
2338
+
2339
+ function onMouseMove(e) {
2340
+ if (options.grid.hoverable)
2341
+ triggerClickHoverEvent("plothover", e,
2342
+ function (s) { return s["hoverable"] != false; });
2343
+ }
2344
+
2345
+ function onMouseLeave(e) {
2346
+ if (options.grid.hoverable)
2347
+ triggerClickHoverEvent("plothover", e,
2348
+ function (s) { return false; });
2349
+ }
2350
+
2351
+ function onClick(e) {
2352
+ triggerClickHoverEvent("plotclick", e,
2353
+ function (s) { return s["clickable"] != false; });
2354
+ }
2355
+
2356
+ // trigger click or hover event (they send the same parameters
2357
+ // so we share their code)
2358
+ function triggerClickHoverEvent(eventname, event, seriesFilter) {
2359
+ var offset = eventHolder.offset(),
2360
+ canvasX = event.pageX - offset.left - plotOffset.left,
2361
+ canvasY = event.pageY - offset.top - plotOffset.top,
2362
+ pos = canvasToAxisCoords({ left: canvasX, top: canvasY });
2363
+
2364
+ pos.pageX = event.pageX;
2365
+ pos.pageY = event.pageY;
2366
+
2367
+ var item = findNearbyItem(canvasX, canvasY, seriesFilter);
2368
+
2369
+ if (item) {
2370
+ // fill in mouse pos for any listeners out there
2371
+ item.pageX = parseInt(item.series.xaxis.p2c(item.datapoint[0]) + offset.left + plotOffset.left);
2372
+ item.pageY = parseInt(item.series.yaxis.p2c(item.datapoint[1]) + offset.top + plotOffset.top);
2373
+ }
2374
+
2375
+ if (options.grid.autoHighlight) {
2376
+ // clear auto-highlights
2377
+ for (var i = 0; i < highlights.length; ++i) {
2378
+ var h = highlights[i];
2379
+ if (h.auto == eventname &&
2380
+ !(item && h.series == item.series &&
2381
+ h.point[0] == item.datapoint[0] &&
2382
+ h.point[1] == item.datapoint[1]))
2383
+ unhighlight(h.series, h.point);
2384
+ }
2385
+
2386
+ if (item)
2387
+ highlight(item.series, item.datapoint, eventname);
2388
+ }
2389
+
2390
+ placeholder.trigger(eventname, [ pos, item ]);
2391
+ }
2392
+
2393
+ function triggerRedrawOverlay() {
2394
+ if (!redrawTimeout)
2395
+ redrawTimeout = setTimeout(drawOverlay, 30);
2396
+ }
2397
+
2398
+ function drawOverlay() {
2399
+ redrawTimeout = null;
2400
+
2401
+ // draw highlights
2402
+ octx.save();
2403
+ octx.clearRect(0, 0, canvasWidth, canvasHeight);
2404
+ octx.translate(plotOffset.left, plotOffset.top);
2405
+
2406
+ var i, hi;
2407
+ for (i = 0; i < highlights.length; ++i) {
2408
+ hi = highlights[i];
2409
+
2410
+ if (hi.series.bars.show)
2411
+ drawBarHighlight(hi.series, hi.point);
2412
+ else
2413
+ drawPointHighlight(hi.series, hi.point);
2414
+ }
2415
+ octx.restore();
2416
+
2417
+ executeHooks(hooks.drawOverlay, [octx]);
2418
+ }
2419
+
2420
+ function highlight(s, point, auto) {
2421
+ if (typeof s == "number")
2422
+ s = series[s];
2423
+
2424
+ if (typeof point == "number") {
2425
+ var ps = s.datapoints.pointsize;
2426
+ point = s.datapoints.points.slice(ps * point, ps * (point + 1));
2427
+ }
2428
+
2429
+ var i = indexOfHighlight(s, point);
2430
+ if (i == -1) {
2431
+ highlights.push({ series: s, point: point, auto: auto });
2432
+
2433
+ triggerRedrawOverlay();
2434
+ }
2435
+ else if (!auto)
2436
+ highlights[i].auto = false;
2437
+ }
2438
+
2439
+ function unhighlight(s, point) {
2440
+ if (s == null && point == null) {
2441
+ highlights = [];
2442
+ triggerRedrawOverlay();
2443
+ }
2444
+
2445
+ if (typeof s == "number")
2446
+ s = series[s];
2447
+
2448
+ if (typeof point == "number")
2449
+ point = s.data[point];
2450
+
2451
+ var i = indexOfHighlight(s, point);
2452
+ if (i != -1) {
2453
+ highlights.splice(i, 1);
2454
+
2455
+ triggerRedrawOverlay();
2456
+ }
2457
+ }
2458
+
2459
+ function indexOfHighlight(s, p) {
2460
+ for (var i = 0; i < highlights.length; ++i) {
2461
+ var h = highlights[i];
2462
+ if (h.series == s && h.point[0] == p[0]
2463
+ && h.point[1] == p[1])
2464
+ return i;
2465
+ }
2466
+ return -1;
2467
+ }
2468
+
2469
+ function drawPointHighlight(series, point) {
2470
+ var x = point[0], y = point[1],
2471
+ axisx = series.xaxis, axisy = series.yaxis;
2472
+
2473
+ if (x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max)
2474
+ return;
2475
+
2476
+ var pointRadius = series.points.radius + series.points.lineWidth / 2;
2477
+ octx.lineWidth = pointRadius;
2478
+ octx.strokeStyle = $.color.parse(series.color).scale('a', 0.5).toString();
2479
+ var radius = 1.5 * pointRadius,
2480
+ x = axisx.p2c(x),
2481
+ y = axisy.p2c(y);
2482
+
2483
+ octx.beginPath();
2484
+ if (series.points.symbol == "circle")
2485
+ octx.arc(x, y, radius, 0, 2 * Math.PI, false);
2486
+ else
2487
+ series.points.symbol(octx, x, y, radius, false);
2488
+ octx.closePath();
2489
+ octx.stroke();
2490
+ }
2491
+
2492
+ function drawBarHighlight(series, point) {
2493
+ octx.lineWidth = series.bars.lineWidth;
2494
+ octx.strokeStyle = $.color.parse(series.color).scale('a', 0.5).toString();
2495
+ var fillStyle = $.color.parse(series.color).scale('a', 0.5).toString();
2496
+ var barLeft = series.bars.align == "left" ? 0 : -series.bars.barWidth/2;
2497
+ drawBar(point[0], point[1], point[2] || 0, barLeft, barLeft + series.bars.barWidth,
2498
+ 0, function () { return fillStyle; }, series.xaxis, series.yaxis, octx, series.bars.horizontal, series.bars.lineWidth);
2499
+ }
2500
+
2501
+ function getColorOrGradient(spec, bottom, top, defaultColor) {
2502
+ if (typeof spec == "string")
2503
+ return spec;
2504
+ else {
2505
+ // assume this is a gradient spec; IE currently only
2506
+ // supports a simple vertical gradient properly, so that's
2507
+ // what we support too
2508
+ var gradient = ctx.createLinearGradient(0, top, 0, bottom);
2509
+
2510
+ for (var i = 0, l = spec.colors.length; i < l; ++i) {
2511
+ var c = spec.colors[i];
2512
+ if (typeof c != "string") {
2513
+ var co = $.color.parse(defaultColor);
2514
+ if (c.brightness != null)
2515
+ co = co.scale('rgb', c.brightness)
2516
+ if (c.opacity != null)
2517
+ co.a *= c.opacity;
2518
+ c = co.toString();
2519
+ }
2520
+ gradient.addColorStop(i / (l - 1), c);
2521
+ }
2522
+
2523
+ return gradient;
2524
+ }
2525
+ }
2526
+ }
2527
+
2528
+ $.plot = function(placeholder, data, options) {
2529
+ //var t0 = new Date();
2530
+ var plot = new Plot($(placeholder), data, options, $.plot.plugins);
2531
+ //(window.console ? console.log : alert)("time used (msecs): " + ((new Date()).getTime() - t0.getTime()));
2532
+ return plot;
2533
+ };
2534
+
2535
+ $.plot.version = "0.7";
2536
+
2537
+ $.plot.plugins = [];
2538
+
2539
+ // returns a string with the date d formatted according to fmt
2540
+ $.plot.formatDate = function(d, fmt, monthNames) {
2541
+ var leftPad = function(n) {
2542
+ n = "" + n;
2543
+ return n.length == 1 ? "0" + n : n;
2544
+ };
2545
+
2546
+ var r = [];
2547
+ var escape = false, padNext = false;
2548
+ var hours = d.getUTCHours();
2549
+ var isAM = hours < 12;
2550
+ if (monthNames == null)
2551
+ monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
2552
+
2553
+ if (fmt.search(/%p|%P/) != -1) {
2554
+ if (hours > 12) {
2555
+ hours = hours - 12;
2556
+ } else if (hours == 0) {
2557
+ hours = 12;
2558
+ }
2559
+ }
2560
+ for (var i = 0; i < fmt.length; ++i) {
2561
+ var c = fmt.charAt(i);
2562
+
2563
+ if (escape) {
2564
+ switch (c) {
2565
+ case 'h': c = "" + hours; break;
2566
+ case 'H': c = leftPad(hours); break;
2567
+ case 'M': c = leftPad(d.getUTCMinutes()); break;
2568
+ case 'S': c = leftPad(d.getUTCSeconds()); break;
2569
+ case 'd': c = "" + d.getUTCDate(); break;
2570
+ case 'm': c = "" + (d.getUTCMonth() + 1); break;
2571
+ case 'y': c = "" + d.getUTCFullYear(); break;
2572
+ case 'b': c = "" + monthNames[d.getUTCMonth()]; break;
2573
+ case 'p': c = (isAM) ? ("" + "am") : ("" + "pm"); break;
2574
+ case 'P': c = (isAM) ? ("" + "AM") : ("" + "PM"); break;
2575
+ case '0': c = ""; padNext = true; break;
2576
+ }
2577
+ if (c && padNext) {
2578
+ c = leftPad(c);
2579
+ padNext = false;
2580
+ }
2581
+ r.push(c);
2582
+ if (!padNext)
2583
+ escape = false;
2584
+ }
2585
+ else {
2586
+ if (c == "%")
2587
+ escape = true;
2588
+ else
2589
+ r.push(c);
2590
+ }
2591
+ }
2592
+ return r.join("");
2593
+ };
2594
+
2595
+ // round to nearby lower multiple of base
2596
+ function floorInBase(n, base) {
2597
+ return base * Math.floor(n / base);
2598
+ }
2599
+
2600
+ })(jQuery);