PageRenderTime 45ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/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

Large files files are truncated, but you can click here to view the full file

  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 s…

Large files files are truncated, but you can click here to view the full file