PageRenderTime 63ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 1ms

/Demo/jquery/jquery-flot.js

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