flot.js 103 KB

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