vendor: 49,183 bytes, lines 1-1263
1/* Javascript plotting library for jQuery, version 0.8.0. 2 3Copyright (c) 2007-2013 IOLA and Ole Laursen. 4Licensed under the MIT license. 5 6*/ 7 8// first an inline dependency, jquery.colorhelpers.js, we inline it here 9// for convenience 10 11/* Plugin for jQuery for working with colors. 12 * 13 * Version 1.1. 14 * 15 * Inspiration from jQuery color animation plugin by John Resig. 16 * 17 * Released under the MIT license by Ole Laursen, October 2009. 18 * 19 * Examples: 20 * 21 * $.color.parse("#fff").scale('rgb', 0.25).add('a', -0.5).toString() 22 * var c = $.color.extract($("#mydiv"), 'background-color'); 23 * console.log(c.r, c.g, c.b, c.a); 24 * $.color.make(100, 50, 25, 0.4).toString() // returns "rgba(100,50,25,0.4)" 25 * 26 * Note that .scale() and .add() return the same modified object 27 * instead of making a new one. 28 * 29 * V. 1.1: Fix error handling so e.g. parsing an empty string does 30 * produce a color rather than just crashing. 31 */ 32(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); 33 34// the actual Flot code 35(function($) { 36 37 // Cache the prototype hasOwnProperty for faster access 38 39 var hasOwnProperty = Object.prototype.hasOwnProperty; 40 41 /////////////////////////////////////////////////////////////////////////// 42 // The Canvas object is a wrapper around an HTML5 <canvas> tag. 43 // 44 // @constructor 45 // @param {string} cls List of classes to apply to the canvas. 46 // @param {element} container Element onto which to append the canvas. 47 // 48 // Requiring a container is a little iffy, but unfortunately canvas 49 // operations don't work unless the canvas is attached to the DOM. 50 51 function Canvas(cls, container) { 52 53 var element = container.children("." + cls)[0]; 54 55 if (element == null) { 56 57 element = document.createElement("canvas"); 58 element.className = cls; 59 60 $(element).css({ direction: "ltr", position: "absolute", left: 0, top: 0 }) 61 .appendTo(container); 62 63 // If HTML5 Canvas isn't available, fall back to [Ex|Flash]canvas 64 65 if (!element.getContext) { 66 if (window.G_vmlCanvasManager) { 67 element = window.G_vmlCanvasManager.initElement(element); 68 } else { 69 throw new Error("Canvas is not available. If you're using IE with a fall-back such as Excanvas, then there's either a mistake in your conditional include, or the page has no DOCTYPE and is rendering in Quirks Mode."); 70 } 71 } 72 } 73 74 this.element = element; 75 76 var context = this.context = element.getContext("2d"); 77 78 // Determine the screen's ratio of physical to device-independent 79 // pixels. This is the ratio between the canvas width that the browser 80 // advertises and the number of pixels actually present in that space. 81 82 // The iPhone 4, for example, has a device-independent width of 320px, 83 // but its screen is actually 640px wide. It therefore has a pixel 84 // ratio of 2, while most normal devices have a ratio of 1. 85 86 var devicePixelRatio = window.devicePixelRatio || 1, 87 backingStoreRatio = 88 context.webkitBackingStorePixelRatio || 89 context.mozBackingStorePixelRatio || 90 context.msBackingStorePixelRatio || 91 context.oBackingStorePixelRatio || 92 context.backingStorePixelRatio || 1; 93 94 this.pixelRatio = devicePixelRatio / backingStoreRatio; 95 96 // Size the canvas to match the internal dimensions of its container 97 98 this.resize(container.width(), container.height()); 99 100 // Collection of HTML div layers for text overlaid onto the canvas 101 102 this.textContainer = null; 103 this.text = {}; 104 105 // Cache of text fragments and metrics, so we can avoid expensively 106 // re-calculating them when the plot is re-rendered in a loop. 107 108 this._textCache = {}; 109 } 110 111 // Resizes the canvas to the given dimensions. 112 // 113 // @param {number} width New width of the canvas, in pixels. 114 // @param {number} width New height of the canvas, in pixels. 115 116 Canvas.prototype.resize = function(width, height) { 117 118 if (width <= 0 || height <= 0) { 119 throw new Error("Invalid dimensions for plot, width = " + width + ", height = " + height); 120 } 121 122 var element = this.element, 123 context = this.context, 124 pixelRatio = this.pixelRatio; 125 126 // Resize the canvas, increasing its density based on the display's 127 // pixel ratio; basically giving it more pixels without increasing the 128 // size of its element, to take advantage of the fact that retina 129 // displays have that many more pixels in the same advertised space. 130 131 // Resizing should reset the state (excanvas seems to be buggy though) 132 133 if (this.width != width) { 134 element.width = width * pixelRatio; 135 element.style.width = width + "px"; 136 this.width = width; 137 } 138 139 if (this.height != height) { 140 element.height = height * pixelRatio; 141 element.style.height = height + "px"; 142 this.height = height; 143 } 144 145 // Save the context, so we can reset in case we get replotted. The 146 // restore ensure that we're really back at the initial state, and 147 // should be safe even if we haven't saved the initial state yet. 148 149 context.restore(); 150 context.save(); 151 152 // Scale the coordinate space to match the display density; so even though we 153 // may have twice as many pixels, we still want lines and other drawing to 154 // appear at the same size; the extra pixels will just make them crisper. 155 156 context.scale(pixelRatio, pixelRatio); 157 }; 158 159 // Clears the entire canvas area, not including any overlaid HTML text 160 161 Canvas.prototype.clear = function() { 162 this.context.clearRect(0, 0, this.width, this.height); 163 }; 164 165 // Finishes rendering the canvas, including managing the text overlay. 166 167 Canvas.prototype.render = function() { 168 169 var cache = this._textCache; 170 171 // For each text layer, add elements marked as active that haven't 172 // already been rendered, and remove those that are no longer active. 173 174 for (var layerKey in cache) { 175 if (hasOwnProperty.call(cache, layerKey)) { 176 177 var layer = this.getTextLayer(layerKey), 178 layerCache = cache[layerKey]; 179 180 layer.hide(); 181 182 for (var styleKey in layerCache) { 183 if (hasOwnProperty.call(layerCache, styleKey)) { 184 var styleCache = layerCache[styleKey]; 185 for (var key in styleCache) { 186 if (hasOwnProperty.call(styleCache, key)) { 187 var info = styleCache[key]; 188 if (info.active) { 189 if (!info.rendered) { 190 layer.append(info.element); 191 info.rendered = true; 192 } 193 } else { 194 delete styleCache[key]; 195 if (info.rendered) { 196 info.element.detach(); 197 } 198 } 199 } 200 } 201 } 202 } 203 204 layer.show(); 205 } 206 } 207 }; 208 209 // Creates (if necessary) and returns the text overlay container. 210 // 211 // @param {string} classes String of space-separated CSS classes used to 212 // uniquely identify the text layer. 213 // @return {object} The jQuery-wrapped text-layer div. 214 215 Canvas.prototype.getTextLayer = function(classes) { 216 217 var layer = this.text[classes]; 218 219 // Create the text layer if it doesn't exist 220 221 if (layer == null) { 222 223 // Create the text layer container, if it doesn't exist 224 225 if (this.textContainer == null) { 226 this.textContainer = $("<div class='flot-text'></div>") 227 .css({ 228 position: "absolute", 229 top: 0, 230 left: 0, 231 bottom: 0, 232 right: 0, 233 'font-size': "smaller", 234 color: "#545454" 235 }) 236 .insertAfter(this.element); 237 } 238 239 layer = this.text[classes] = $("<div></div>") 240 .addClass(classes) 241 .css({ 242 position: "absolute", 243 top: 0, 244 left: 0, 245 bottom: 0, 246 right: 0 247 }) 248 .appendTo(this.textContainer); 249 } 250 251 return layer; 252 }; 253 254 // Creates (if necessary) and returns a text info object. 255 // 256 // The object looks like this: 257 // 258 // { 259 // width: Width of the text's wrapper div. 260 // height: Height of the text's wrapper div. 261 // active: Flag indicating whether the text should be visible. 262 // rendered: Flag indicating whether the text is currently visible. 263 // element: The jQuery-wrapped HTML div containing the text. 264 // } 265 // 266 // Canvas maintains a cache of recently-used text info objects; getTextInfo 267 // either returns the cached element or creates a new entry. 268 // 269 // @param {string} layer A string of space-separated CSS classes uniquely 270 // identifying the layer containing this text. 271 // @param {string} text Text string to retrieve info for. 272 // @param {(string|object)=} font Either a string of space-separated CSS 273 // classes or a font-spec object, defining the text's font and style. 274 // @param {number=} angle Angle at which to rotate the text, in degrees. 275 // Angle is currently unused, it will be implemented in the future. 276 // @return {object} a text info object. 277 278 Canvas.prototype.getTextInfo = function(layer, text, font, angle) { 279 280 var textStyle, layerCache, styleCache, info; 281 282 // Cast the value to a string, in case we were given a number or such 283 284 text = "" + text; 285 286 // If the font is a font-spec object, generate a CSS font definition 287 288 if (typeof font === "object") { 289 textStyle = font.style + " " + font.variant + " " + font.weight + " " + font.size + "px/" + font.lineHeight + "px " + font.family; 290 } else { 291 textStyle = font; 292 } 293 294 // Retrieve (or create) the cache for the text's layer and styles 295 296 layerCache = this._textCache[layer]; 297 298 if (layerCache == null) { 299 layerCache = this._textCache[layer] = {}; 300 } 301 302 styleCache = layerCache[textStyle]; 303 304 if (styleCache == null) { 305 styleCache = layerCache[textStyle] = {}; 306 } 307 308 info = styleCache[text]; 309 310 // If we can't find a matching element in our cache, create a new one 311 312 if (info == null) { 313 314 var element = $("<div></div>").html(text) 315 .css({ 316 position: "absolute", 317 top: -9999 318 }) 319 .appendTo(this.getTextLayer(layer)); 320 321 if (typeof font === "object") { 322 element.css({ 323 font: textStyle, 324 color: font.color 325 }); 326 } else if (typeof font === "string") { 327 element.addClass(font); 328 } 329 330 info = styleCache[text] = { 331 active: false, 332 rendered: false, 333 element: element, 334 width: element.outerWidth(true), 335 height: element.outerHeight(true) 336 }; 337 338 element.detach(); 339 } 340 341 return info; 342 }; 343 344 // Adds a text string to the canvas text overlay. 345 // 346 // The text isn't drawn immediately; it is marked as rendering, which will 347 // result in its addition to the canvas on the next render pass. 348 // 349 // @param {string} layer A string of space-separated CSS classes uniquely 350 // identifying the layer containing this text. 351 // @param {number} x X coordinate at which to draw the text. 352 // @param {number} y Y coordinate at which to draw the text. 353 // @param {string} text Text string to draw. 354 // @param {(string|object)=} font Either a string of space-separated CSS 355 // classes or a font-spec object, defining the text's font and style. 356 // @param {number=} angle Angle at which to rotate the text, in degrees. 357 // Angle is currently unused, it will be implemented in the future. 358 // @param {string=} halign Horizontal alignment of the text; either "left", 359 // "center" or "right". 360 // @param {string=} valign Vertical alignment of the text; either "top", 361 // "middle" or "bottom". 362 363 Canvas.prototype.addText = function(layer, x, y, text, font, angle, halign, valign) { 364 365 var info = this.getTextInfo(layer, text, font, angle); 366 367 // Mark the div for inclusion in the next render pass 368 369 info.active = true; 370 371 // Tweak the div's position to match the text's alignment 372 373 if (halign == "center") { 374 x -= info.width / 2; 375 } else if (halign == "right") { 376 x -= info.width; 377 } 378 379 if (valign == "middle") { 380 y -= info.height / 2; 381 } else if (valign == "bottom") { 382 y -= info.height; 383 } 384 385 // Move the element to its final position within the container 386 387 info.element.css({ 388 top: Math.round(y), 389 left: Math.round(x) 390 }); 391 }; 392 393 // Removes one or more text strings from the canvas text overlay. 394 // 395 // If no parameters are given, all text within the layer is removed. 396 // The text is not actually removed; it is simply marked as inactive, which 397 // will result in its removal on the next render pass. 398 // 399 // @param {string} layer A string of space-separated CSS classes uniquely 400 // identifying the layer containing this text. 401 // @param {string} text Text string to remove. 402 // @param {(string|object)=} font Either a string of space-separated CSS 403 // classes or a font-spec object, defining the text's font and style. 404 // @param {number=} angle Angle at which the text is rotated, in degrees. 405 // Angle is currently unused, it will be implemented in the future. 406 407 Canvas.prototype.removeText = function(layer, text, font, angle) { 408 if (text == null) { 409 var layerCache = this._textCache[layer]; 410 if (layerCache != null) { 411 for (var styleKey in layerCache) { 412 if (hasOwnProperty.call(layerCache, styleKey)) { 413 var styleCache = layerCache[styleKey] 414 for (var key in styleCache) { 415 if (hasOwnProperty.call(styleCache, key)) { 416 styleCache[key].active = false; 417 } 418 } 419 } 420 } 421 } 422 } else { 423 this.getTextInfo(layer, text, font, angle).active = false; 424 } 425 }; 426 427 /////////////////////////////////////////////////////////////////////////// 428 // The top-level container for the entire plot. 429 430 function Plot(placeholder, data_, options_, plugins) { 431 // data is on the form: 432 // [ series1, series2 ... ] 433 // where series is either just the data as [ [x1, y1], [x2, y2], ... ] 434 // or { data: [ [x1, y1], [x2, y2], ... ], label: "some label", ... } 435 436 var series = [], 437 options = { 438 // the color theme used for graphs 439 colors: ["#edc240", "#afd8f8", "#cb4b4b", "#4da74d", "#9440ed"], 440 legend: { 441 show: true, 442 noColumns: 1, // number of colums in legend table 443 labelFormatter: null, // fn: string -> string 444 labelBoxBorderColor: "#ccc", // border color for the little label boxes 445 container: null, // container (as jQuery object) to put legend in, null means default on top of graph 446 position: "ne", // position of default legend container within plot 447 margin: 5, // distance from grid edge to default legend container within plot 448 backgroundColor: null, // null means auto-detect 449 backgroundOpacity: 0.85, // set to 0 to avoid background 450 sorted: null // default to no legend sorting 451 }, 452 xaxis: { 453 show: null, // null = auto-detect, true = always, false = never 454 position: "bottom", // or "top" 455 mode: null, // null or "time" 456 font: null, // null (derived from CSS in placeholder) or object like { size: 11, lineHeight: 13, style: "italic", weight: "bold", family: "sans-serif", variant: "small-caps" } 457 color: null, // base color, labels, ticks 458 tickColor: null, // possibly different color of ticks, e.g. "rgba(0,0,0,0.15)" 459 transform: null, // null or f: number -> number to transform axis 460 inverseTransform: null, // if transform is set, this should be the inverse function 461 min: null, // min. value to show, null means set automatically 462 max: null, // max. value to show, null means set automatically 463 autoscaleMargin: null, // margin in % to add if auto-setting min/max 464 ticks: null, // either [1, 3] or [[1, "a"], 3] or (fn: axis info -> ticks) or app. number of ticks for auto-ticks 465 tickFormatter: null, // fn: number -> string 466 labelWidth: null, // size of tick labels in pixels 467 labelHeight: null, 468 reserveSpace: null, // whether to reserve space even if axis isn't shown 469 tickLength: null, // size in pixels of ticks, or "full" for whole line 470 alignTicksWithAxis: null, // axis number or null for no sync 471 tickDecimals: null, // no. of decimals, null means auto 472 tickSize: null, // number or [number, "unit"] 473 minTickSize: null // number or [number, "unit"] 474 }, 475 yaxis: { 476 autoscaleMargin: 0.02, 477 position: "left" // or "right" 478 }, 479 xaxes: [], 480 yaxes: [], 481 series: { 482 points: { 483 show: false, 484 radius: 3, 485 lineWidth: 2, // in pixels 486 fill: true, 487 fillColor: "#ffffff", 488 symbol: "circle" // or callback 489 }, 490 lines: { 491 // we don't put in show: false so we can see 492 // whether lines were actively disabled 493 lineWidth: 2, // in pixels 494 fill: false, 495 fillColor: null, 496 steps: false 497 // Omit 'zero', so we can later default its value to 498 // match that of the 'fill' option. 499 }, 500 bars: { 501 show: false, 502 lineWidth: 2, // in pixels 503 barWidth: 1, // in units of the x axis 504 fill: true, 505 fillColor: null, 506 align: "left", // "left", "right", or "center" 507 horizontal: false, 508 zero: true 509 }, 510 shadowSize: 3, 511 highlightColor: null 512 }, 513 grid: { 514 show: true, 515 aboveData: false, 516 color: "#545454", // primary color used for outline and labels 517 backgroundColor: null, // null for transparent, else color 518 borderColor: null, // set if different from the grid color 519 tickColor: null, // color for the ticks, e.g. "rgba(0,0,0,0.15)" 520 margin: 0, // distance from the canvas edge to the grid 521 labelMargin: 5, // in pixels 522 axisMargin: 8, // in pixels 523 borderWidth: 2, // in pixels 524 minBorderMargin: null, // in pixels, null means taken from points radius 525 markings: null, // array of ranges or fn: axes -> array of ranges 526 markingsColor: "#f4f4f4", 527 markingsLineWidth: 2, 528 // interactive stuff 529 clickable: false, 530 hoverable: false, 531 autoHighlight: true, // highlight in case mouse is near 532 mouseActiveRadius: 10 // how far the mouse can be away to activate an item 533 }, 534 interaction: { 535 redrawOverlayInterval: 1000/60 // time between updates, -1 means in same flow 536 }, 537 hooks: {} 538 }, 539 surface = null, // the canvas for the plot itself 540 overlay = null, // canvas for interactive stuff on top of plot 541 eventHolder = null, // jQuery object that events should be bound to 542 ctx = null, octx = null, 543 xaxes = [], yaxes = [], 544 plotOffset = { left: 0, right: 0, top: 0, bottom: 0}, 545 plotWidth = 0, plotHeight = 0, 546 hooks = { 547 processOptions: [], 548 processRawData: [], 549 processDatapoints: [], 550 processOffset: [], 551 drawBackground: [], 552 drawSeries: [], 553 draw: [], 554 bindEvents: [], 555 drawOverlay: [], 556 shutdown: [] 557 }, 558 plot = this; 559 560 // public functions 561 plot.setData = setData; 562 plot.setupGrid = setupGrid; 563 plot.draw = draw; 564 plot.getPlaceholder = function() { return placeholder; }; 565 plot.getCanvas = function() { return surface.element; }; 566 plot.getPlotOffset = function() { return plotOffset; }; 567 plot.width = function () { return plotWidth; }; 568 plot.height = function () { return plotHeight; }; 569 plot.offset = function () { 570 var o = eventHolder.offset(); 571 o.left += plotOffset.left; 572 o.top += plotOffset.top; 573 return o; 574 }; 575 plot.getData = function () { return series; }; 576 plot.getAxes = function () { 577 var res = {}, i; 578 $.each(xaxes.concat(yaxes), function (_, axis) { 579 if (axis) 580 res[axis.direction + (axis.n != 1 ? axis.n : "") + "axis"] = axis; 581 }); 582 return res; 583 }; 584 plot.getXAxes = function () { return xaxes; }; 585 plot.getYAxes = function () { return yaxes; }; 586 plot.c2p = canvasToAxisCoords; 587 plot.p2c = axisToCanvasCoords; 588 plot.getOptions = function () { return options; }; 589 plot.highlight = highlight; 590 plot.unhighlight = unhighlight; 591 plot.triggerRedrawOverlay = triggerRedrawOverlay; 592 plot.pointOffset = function(point) { 593 return { 594 left: parseInt(xaxes[axisNumber(point, "x") - 1].p2c(+point.x) + plotOffset.left, 10), 595 top: parseInt(yaxes[axisNumber(point, "y") - 1].p2c(+point.y) + plotOffset.top, 10) 596 }; 597 }; 598 plot.shutdown = shutdown; 599 plot.resize = function () { 600 var width = placeholder.width(), 601 height = placeholder.height(); 602 surface.resize(width, height); 603 overlay.resize(width, height); 604 }; 605 606 // public attributes 607 plot.hooks = hooks; 608 609 // initialize 610 initPlugins(plot); 611 parseOptions(options_); 612 setupCanvases(); 613 setData(data_); 614 setupGrid(); 615 draw(); 616 bindEvents(); 617 618 619 function executeHooks(hook, args) { 620 args = [plot].concat(args); 621 for (var i = 0; i < hook.length; ++i) 622 hook[i].apply(this, args); 623 } 624 625 function initPlugins() { 626 627 // References to key classes, allowing plugins to modify them 628 629 var classes = { 630 Canvas: Canvas 631 }; 632 633 for (var i = 0; i < plugins.length; ++i) { 634 var p = plugins[i]; 635 p.init(plot, classes); 636 if (p.options) 637 $.extend(true, options, p.options); 638 } 639 } 640 641 function parseOptions(opts) { 642 643 $.extend(true, options, opts); 644 645 if (options.xaxis.color == null) 646 options.xaxis.color = $.color.parse(options.grid.color).scale('a', 0.22).toString(); 647 if (options.yaxis.color == null) 648 options.yaxis.color = $.color.parse(options.grid.color).scale('a', 0.22).toString(); 649 650 if (options.xaxis.tickColor == null) // grid.tickColor for back-compatibility 651 options.xaxis.tickColor = options.grid.tickColor || options.xaxis.color; 652 if (options.yaxis.tickColor == null) // grid.tickColor for back-compatibility 653 options.yaxis.tickColor = options.grid.tickColor || options.yaxis.color; 654 655 if (options.grid.borderColor == null) 656 options.grid.borderColor = options.grid.color; 657 if (options.grid.tickColor == null) 658 options.grid.tickColor = $.color.parse(options.grid.color).scale('a', 0.22).toString(); 659 660 // Fill in defaults for axis options, including any unspecified 661 // font-spec fields, if a font-spec was provided. 662 663 // If no x/y axis options were provided, create one of each anyway, 664 // since the rest of the code assumes that they exist. 665 666 var i, axisOptions, axisCount, 667 fontDefaults = { 668 style: placeholder.css("font-style"), 669 size: Math.round(0.8 * (+placeholder.css("font-size").replace("px", "") || 13)), 670 variant: placeholder.css("font-variant"), 671 weight: placeholder.css("font-weight"), 672 family: placeholder.css("font-family") 673 }; 674 675 fontDefaults.lineHeight = fontDefaults.size * 1.15; 676 677 axisCount = options.xaxes.length || 1; 678 for (i = 0; i < axisCount; ++i) { 679 680 axisOptions = options.xaxes[i]; 681 if (axisOptions && !axisOptions.tickColor) { 682 axisOptions.tickColor = axisOptions.color; 683 } 684 685 axisOptions = $.extend(true, {}, options.xaxis, axisOptions); 686 options.xaxes[i] = axisOptions; 687 688 if (axisOptions.font) { 689 axisOptions.font = $.extend({}, fontDefaults, axisOptions.font); 690 if (!axisOptions.font.color) { 691 axisOptions.font.color = axisOptions.color; 692 } 693 } 694 } 695 696 axisCount = options.yaxes.length || 1; 697 for (i = 0; i < axisCount; ++i) { 698 699 axisOptions = options.yaxes[i]; 700 if (axisOptions && !axisOptions.tickColor) { 701 axisOptions.tickColor = axisOptions.color; 702 } 703 704 axisOptions = $.extend(true, {}, options.yaxis, axisOptions); 705 options.yaxes[i] = axisOptions; 706 707 if (axisOptions.font) { 708 axisOptions.font = $.extend({}, fontDefaults, axisOptions.font); 709 if (!axisOptions.font.color) { 710 axisOptions.font.color = axisOptions.color; 711 } 712 } 713 } 714 715 // backwards compatibility, to be removed in future 716 if (options.xaxis.noTicks && options.xaxis.ticks == null) 717 options.xaxis.ticks = options.xaxis.noTicks; 718 if (options.yaxis.noTicks && options.yaxis.ticks == null) 719 options.yaxis.ticks = options.yaxis.noTicks; 720 if (options.x2axis) { 721 options.xaxes[1] = $.extend(true, {}, options.xaxis, options.x2axis); 722 options.xaxes[1].position = "top"; 723 } 724 if (options.y2axis) { 725 options.yaxes[1] = $.extend(true, {}, options.yaxis, options.y2axis); 726 options.yaxes[1].position = "right"; 727 } 728 if (options.grid.coloredAreas) 729 options.grid.markings = options.grid.coloredAreas; 730 if (options.grid.coloredAreasColor) 731 options.grid.markingsColor = options.grid.coloredAreasColor; 732 if (options.lines) 733 $.extend(true, options.series.lines, options.lines); 734 if (options.points) 735 $.extend(true, options.series.points, options.points); 736 if (options.bars) 737 $.extend(true, options.series.bars, options.bars); 738 if (options.shadowSize != null) 739 options.series.shadowSize = options.shadowSize; 740 if (options.highlightColor != null) 741 options.series.highlightColor = options.highlightColor; 742 743 // save options on axes for future reference 744 for (i = 0; i < options.xaxes.length; ++i) 745 getOrCreateAxis(xaxes, i + 1).options = options.xaxes[i]; 746 for (i = 0; i < options.yaxes.length; ++i) 747 getOrCreateAxis(yaxes, i + 1).options = options.yaxes[i]; 748 749 // add hooks from options 750 for (var n in hooks) 751 if (options.hooks[n] && options.hooks[n].length) 752 hooks[n] = hooks[n].concat(options.hooks[n]); 753 754 executeHooks(hooks.processOptions, [options]); 755 } 756 757 function setData(d) { 758 series = parseData(d); 759 fillInSeriesOptions(); 760 processData(); 761 } 762 763 function parseData(d) { 764 var res = []; 765 for (var i = 0; i < d.length; ++i) { 766 var s = $.extend(true, {}, options.series); 767 768 if (d[i].data != null) { 769 s.data = d[i].data; // move the data instead of deep-copy 770 delete d[i].data; 771 772 $.extend(true, s, d[i]); 773 774 d[i].data = s.data; 775 } 776 else 777 s.data = d[i]; 778 res.push(s); 779 } 780 781 return res; 782 } 783 784 function axisNumber(obj, coord) { 785 var a = obj[coord + "axis"]; 786 if (typeof a == "object") // if we got a real axis, extract number 787 a = a.n; 788 if (typeof a != "number") 789 a = 1; // default to first axis 790 return a; 791 } 792 793 function allAxes() { 794 // return flat array without annoying null entries 795 return $.grep(xaxes.concat(yaxes), function (a) { return a; }); 796 } 797 798 function canvasToAxisCoords(pos) { 799 // return an object with x/y corresponding to all used axes 800 var res = {}, i, axis; 801 for (i = 0; i < xaxes.length; ++i) { 802 axis = xaxes[i]; 803 if (axis && axis.used) 804 res["x" + axis.n] = axis.c2p(pos.left); 805 } 806 807 for (i = 0; i < yaxes.length; ++i) { 808 axis = yaxes[i]; 809 if (axis && axis.used) 810 res["y" + axis.n] = axis.c2p(pos.top); 811 } 812 813 if (res.x1 !== undefined) 814 res.x = res.x1; 815 if (res.y1 !== undefined) 816 res.y = res.y1; 817 818 return res; 819 } 820 821 function axisToCanvasCoords(pos) { 822 // get canvas coords from the first pair of x/y found in pos 823 var res = {}, i, axis, key; 824 825 for (i = 0; i < xaxes.length; ++i) { 826 axis = xaxes[i]; 827 if (axis && axis.used) { 828 key = "x" + axis.n; 829 if (pos[key] == null && axis.n == 1) 830 key = "x"; 831 832 if (pos[key] != null) { 833 res.left = axis.p2c(pos[key]); 834 break; 835 } 836 } 837 } 838 839 for (i = 0; i < yaxes.length; ++i) { 840 axis = yaxes[i]; 841 if (axis && axis.used) { 842 key = "y" + axis.n; 843 if (pos[key] == null && axis.n == 1) 844 key = "y"; 845 846 if (pos[key] != null) { 847 res.top = axis.p2c(pos[key]); 848 break; 849 } 850 } 851 } 852 853 return res; 854 } 855 856 function getOrCreateAxis(axes, number) { 857 if (!axes[number - 1]) 858 axes[number - 1] = { 859 n: number, // save the number for future reference 860 direction: axes == xaxes ? "x" : "y", 861 options: $.extend(true, {}, axes == xaxes ? options.xaxis : options.yaxis) 862 }; 863 864 return axes[number - 1]; 865 } 866 867 function fillInSeriesOptions() { 868 869 var neededColors = series.length, maxIndex = -1, i; 870 871 // Subtract the number of series that already have fixed colors or 872 // color indexes from the number that we still need to generate. 873 874 for (i = 0; i < series.length; ++i) { 875 var sc = series[i].color; 876 if (sc != null) { 877 neededColors--; 878 if (typeof sc == "number" && sc > maxIndex) { 879 maxIndex = sc; 880 } 881 } 882 } 883 884 // If any of the series have fixed color indexes, then we need to 885 // generate at least as many colors as the highest index. 886 887 if (neededColors <= maxIndex) { 888 neededColors = maxIndex + 1; 889 } 890 891 // Generate all the colors, using first the option colors and then 892 // variations on those colors once they're exhausted. 893 894 var c, colors = [], colorPool = options.colors, 895 colorPoolSize = colorPool.length, variation = 0; 896 897 for (i = 0; i < neededColors; i++) { 898 899 c = $.color.parse(colorPool[i % colorPoolSize] || "#666"); 900 901 // Each time we exhaust the colors in the pool we adjust 902 // a scaling factor used to produce more variations on 903 // those colors. The factor alternates negative/positive 904 // to produce lighter/darker colors. 905 906 // Reset the variation after every few cycles, or else 907 // it will end up producing only white or black colors. 908 909 if (i % colorPoolSize == 0 && i) { 910 if (variation >= 0) { 911 if (variation < 0.5) { 912 variation = -variation - 0.2; 913 } else variation = 0; 914 } else variation = -variation; 915 } 916 917 colors[i] = c.scale('rgb', 1 + variation); 918 } 919 920 // Finalize the series options, filling in their colors 921 922 var colori = 0, s; 923 for (i = 0; i < series.length; ++i) { 924 s = series[i]; 925 926 // assign colors 927 if (s.color == null) { 928 s.color = colors[colori].toString(); 929 ++colori; 930 } 931 else if (typeof s.color == "number") 932 s.color = colors[s.color].toString(); 933 934 // turn on lines automatically in case nothing is set 935 if (s.lines.show == null) { 936 var v, show = true; 937 for (v in s) 938 if (s[v] && s[v].show) { 939 show = false; 940 break; 941 } 942 if (show) 943 s.lines.show = true; 944 } 945 946 // If nothing was provided for lines.zero, default it to match 947 // lines.fill, since areas by default should extend to zero. 948 949 if (s.lines.zero == null) { 950 s.lines.zero = !!s.lines.fill; 951 } 952 953 // setup axes 954 s.xaxis = getOrCreateAxis(xaxes, axisNumber(s, "x")); 955 s.yaxis = getOrCreateAxis(yaxes, axisNumber(s, "y")); 956 } 957 } 958 959 function processData() { 960 var topSentry = Number.POSITIVE_INFINITY, 961 bottomSentry = Number.NEGATIVE_INFINITY, 962 fakeInfinity = Number.MAX_VALUE, 963 i, j, k, m, length, 964 s, points, ps, x, y, axis, val, f, p, 965 data, format; 966 967 function updateAxis(axis, min, max) { 968 if (min < axis.datamin && min != -fakeInfinity) 969 axis.datamin = min; 970 if (max > axis.datamax && max != fakeInfinity) 971 axis.datamax = max; 972 } 973 974 $.each(allAxes(), function (_, axis) { 975 // init axis 976 axis.datamin = topSentry; 977 axis.datamax = bottomSentry; 978 axis.used = false; 979 }); 980 981 for (i = 0; i < series.length; ++i) { 982 s = series[i]; 983 s.datapoints = { points: [] }; 984 985 executeHooks(hooks.processRawData, [ s, s.data, s.datapoints ]); 986 } 987 988 // first pass: clean and copy data 989 for (i = 0; i < series.length; ++i) { 990 s = series[i]; 991 992 data = s.data; 993 format = s.datapoints.format; 994 995 if (!format) { 996 format = []; 997 // find out how to copy 998 format.push({ x: true, number: true, required: true }); 999 format.push({ y: true, number: true, required: true }); 1000 1001 if (s.bars.show || (s.lines.show && s.lines.fill)) { 1002 var autoscale = !!((s.bars.show && s.bars.zero) || (s.lines.show && s.lines.zero)); 1003 format.push({ y: true, number: true, required: false, defaultValue: 0, autoscale: autoscale }); 1004 if (s.bars.horizontal) { 1005 delete format[format.length - 1].y; 1006 format[format.length - 1].x = true; 1007 } 1008 } 1009 1010 s.datapoints.format = format; 1011 } 1012 1013 if (s.datapoints.pointsize != null) 1014 continue; // already filled in 1015 1016 s.datapoints.pointsize = format.length; 1017 1018 ps = s.datapoints.pointsize; 1019 points = s.datapoints.points; 1020 1021 var insertSteps = s.lines.show && s.lines.steps; 1022 s.xaxis.used = s.yaxis.used = true; 1023 1024 for (j = k = 0; j < data.length; ++j, k += ps) { 1025 p = data[j]; 1026 1027 var nullify = p == null; 1028 if (!nullify) { 1029 for (m = 0; m < ps; ++m) { 1030 val = p[m]; 1031 f = format[m]; 1032 1033 if (f) { 1034 if (f.number && val != null) { 1035 val = +val; // convert to number 1036 if (isNaN(val)) 1037 val = null; 1038 else if (val == Infinity) 1039 val = fakeInfinity; 1040 else if (val == -Infinity) 1041 val = -fakeInfinity; 1042 } 1043 1044 if (val == null) { 1045 if (f.required) 1046 nullify = true; 1047 1048 if (f.defaultValue != null) 1049 val = f.defaultValue; 1050 } 1051 } 1052 1053 points[k + m] = val; 1054 } 1055 } 1056 1057 if (nullify) { 1058 for (m = 0; m < ps; ++m) { 1059 val = points[k + m]; 1060 if (val != null) { 1061 f = format[m]; 1062 // extract min/max info 1063 if (f.x) 1064 updateAxis(s.xaxis, val, val); 1065 if (f.y) 1066 updateAxis(s.yaxis, val, val); 1067 } 1068 points[k + m] = null; 1069 } 1070 } 1071 else { 1072 // a little bit of line specific stuff that 1073 // perhaps shouldn't be here, but lacking 1074 // better means... 1075 if (insertSteps && k > 0 1076 && points[k - ps] != null 1077 && points[k - ps] != points[k] 1078 && points[k - ps + 1] != points[k + 1]) { 1079 // copy the point to make room for a middle point 1080 for (m = 0; m < ps; ++m) 1081 points[k + ps + m] = points[k + m]; 1082 1083 // middle point has same y 1084 points[k + 1] = points[k - ps + 1]; 1085 1086 // we've added a point, better reflect that 1087 k += ps; 1088 } 1089 } 1090 } 1091 } 1092 1093 // give the hooks a chance to run 1094 for (i = 0; i < series.length; ++i) { 1095 s = series[i]; 1096 1097 executeHooks(hooks.processDatapoints, [ s, s.datapoints]); 1098 } 1099 1100 // second pass: find datamax/datamin for auto-scaling 1101 for (i = 0; i < series.length; ++i) { 1102 s = series[i]; 1103 points = s.datapoints.points, 1104 ps = s.datapoints.pointsize; 1105 format = s.datapoints.format; 1106 1107 var xmin = topSentry, ymin = topSentry, 1108 xmax = bottomSentry, ymax = bottomSentry; 1109 1110 for (j = 0; j < points.length; j += ps) { 1111 if (points[j] == null) 1112 continue; 1113 1114 for (m = 0; m < ps; ++m) { 1115 val = points[j + m]; 1116 f = format[m]; 1117 if (!f || f.autoscale === false || val == fakeInfinity || val == -fakeInfinity) 1118 continue; 1119 1120 if (f.x) { 1121 if (val < xmin) 1122 xmin = val; 1123 if (val > xmax) 1124 xmax = val; 1125 } 1126 if (f.y) { 1127 if (val < ymin) 1128 ymin = val; 1129 if (val > ymax) 1130 ymax = val; 1131 } 1132 } 1133 } 1134 1135 if (s.bars.show) { 1136 // make sure we got room for the bar on the dancing floor 1137 var delta; 1138 1139 switch (s.bars.align) { 1140 case "left": 1141 delta = 0; 1142 break; 1143 case "right": 1144 delta = -s.bars.barWidth; 1145 break; 1146 case "center": 1147 delta = -s.bars.barWidth / 2; 1148 break; 1149 default: 1150 throw new Error("Invalid bar alignment: " + s.bars.align); 1151 } 1152 1153 if (s.bars.horizontal) { 1154 ymin += delta; 1155 ymax += delta + s.bars.barWidth; 1156 } 1157 else { 1158 xmin += delta; 1159 xmax += delta + s.bars.barWidth; 1160 } 1161 } 1162 1163 updateAxis(s.xaxis, xmin, xmax); 1164 updateAxis(s.yaxis, ymin, ymax); 1165 } 1166 1167 $.each(allAxes(), function (_, axis) { 1168 if (axis.datamin == topSentry) 1169 axis.datamin = null; 1170 if (axis.datamax == bottomSentry) 1171 axis.datamax = null; 1172 }); 1173 } 1174 1175 function setupCanvases() { 1176 1177 // Make sure the placeholder is clear of everything except canvases 1178 // from a previous plot in this container that we'll try to re-use. 1179 1180 placeholder.css("padding", 0) // padding messes up the positioning 1181 .children(":not(.flot-base,.flot-overlay)").remove(); 1182 1183 if (placeholder.css("position") == 'static') 1184 placeholder.css("position", "relative"); // for positioning labels and overlay 1185 1186 surface = new Canvas("flot-base", placeholder); 1187 overlay = new Canvas("flot-overlay", placeholder); // overlay canvas for interactive features 1188 1189 ctx = surface.context; 1190 octx = overlay.context; 1191 1192 // define which element we're listening for events on 1193 eventHolder = $(overlay.element).unbind(); 1194 1195 // If we're re-using a plot object, shut down the old one 1196 1197 var existing = placeholder.data("plot"); 1198 1199 if (existing) { 1200 existing.shutdown(); 1201 overlay.clear(); 1202 } 1203 1204 // save in case we get replotted 1205 placeholder.data("plot", plot); 1206 } 1207 1208 function bindEvents() { 1209 // bind events 1210 if (options.grid.hoverable) { 1211 eventHolder.mousemove(onMouseMove); 1212 1213 // Use bind, rather than .mouseleave, because we officially 1214 // still support jQuery 1.2.6, which doesn't define a shortcut 1215 // for mouseenter or mouseleave. This was a bug/oversight that 1216 // was fixed somewhere around 1.3.x. We can return to using 1217 // .mouseleave when we drop support for 1.2.6. 1218 1219 eventHolder.bind("mouseleave", onMouseLeave); 1220 } 1221 1222 if (options.grid.clickable) 1223 eventHolder.click(onClick); 1224 1225 executeHooks(hooks.bindEvents, [eventHolder]); 1226 } 1227 1228 function shutdown() { 1229 if (redrawTimeout) 1230 clearTimeout(redrawTimeout); 1231 1232 eventHolder.unbind("mousemove", onMouseMove); 1233 eventHolder.unbind("mouseleave", onMouseLeave); 1234 eventHolder.unbind("click", onClick); 1235 1236 executeHooks(hooks.shutdown, [eventHolder]); 1237 } 1238 1239 function setTransformationHelpers(axis) { 1240 // set helper functions on the axis, assumes plot area 1241 // has been computed already 1242 1243 function identity(x) { return x; } 1244 1245 var s, m, t = axis.options.transform || identity, 1246 it = axis.options.inverseTransform; 1247 1248 // precompute how much the axis is scaling a point 1249 // in canvas space 1250 if (axis.direction == "x") { 1251 s = axis.scale = plotWidth / Math.abs(t(axis.max) - t(axis.min)); 1252 m = Math.min(t(axis.max), t(axis.min)); 1253 } 1254 else { 1255 s = axis.scale = plotHeight / Math.abs(t(axis.max) - t(axis.min)); 1256 s = -s; 1257 m = Math.max(t(axis.max), t(axis.min)); 1258 } 1259 1260 // data point to canvas coordinate 1261 if (t == identity) // slight optimization 1262 axis.p2c = function (p) { return (p - m) * s; }; 1263 else
1264 axis.p2c = function (p) { return (t(p) - m) * s; }; 1265 // canvas coordinate to data point 1266 if (!it) 1267 axis.c2p = function (c) { return m + c / s; }; 1268 else 1269 axis.c2p = function (c) { return it(m + c / s); }; 1270 } 1271 1272 function measureTickLabels(axis) { 1273 1274 var opts = axis.options, ticks = axis.ticks || [], 1275 axisw = opts.labelWidth || 0, axish = opts.labelHeight || 0, 1276 legacyStyles = axis.direction + "Axis " + axis.direction + axis.n + "Axis", 1277 layer = "flot-" + axis.direction + "-axis flot-" + axis.direction + axis.n + "-axis " + legacyStyles, 1278 font = opts.font || "flot-tick-label tickLabel"; 1279 1280 for (var i = 0; i < ticks.length; ++i) { 1281 1282 var t = ticks[i]; 1283 1284 if (!t.label) 1285 continue; 1286 1287 var info = surface.getTextInfo(layer, t.label, font); 1288 1289 if (opts.labelWidth == null) 1290 axisw = Math.max(axisw, info.width); 1291 if (opts.labelHeight == null) 1292 axish = Math.max(axish, info.height); 1293 } 1294 1295 axis.labelWidth = Math.ceil(axisw); 1296 axis.labelHeight = Math.ceil(axish); 1297 } 1298 1299 function allocateAxisBoxFirstPhase(axis) { 1300 // find the bounding box of the axis by looking at label 1301 // widths/heights and ticks, make room by diminishing the 1302 // plotOffset; this first phase only looks at one 1303 // dimension per axis, the other dimension depends on the 1304 // other axes so will have to wait 1305 1306 var lw = axis.labelWidth, 1307 lh = axis.labelHeight, 1308 pos = axis.options.position, 1309 tickLength = axis.options.tickLength, 1310 axisMargin = options.grid.axisMargin, 1311 padding = options.grid.labelMargin, 1312 all = axis.direction == "x" ? xaxes : yaxes, 1313 index, innermost; 1314 1315 // determine axis margin 1316 var samePosition = $.grep(all, function (a) { 1317 return a && a.options.position == pos && a.reserveSpace; 1318 }); 1319 if ($.inArray(axis, samePosition) == samePosition.length - 1) 1320 axisMargin = 0; // outermost 1321 1322 // determine tick length - if we're innermost, we can use "full" 1323 if (tickLength == null) { 1324 var sameDirection = $.grep(all, function (a) { 1325 return a && a.reserveSpace; 1326 }); 1327 1328 innermost = $.inArray(axis, sameDirection) == 0; 1329 if (innermost) 1330 tickLength = "full"; 1331 else 1332 tickLength = 5; 1333 } 1334 1335 if (!isNaN(+tickLength)) 1336 padding += +tickLength; 1337 1338 // compute box 1339 if (axis.direction == "x") { 1340 lh += padding; 1341 1342 if (pos == "bottom") { 1343 plotOffset.bottom += lh + axisMargin; 1344 axis.box = { top: surface.height - plotOffset.bottom, height: lh }; 1345 } 1346 else { 1347 axis.box = { top: plotOffset.top + axisMargin, height: lh }; 1348 plotOffset.top += lh + axisMargin; 1349 } 1350 } 1351 else { 1352 lw += padding; 1353 1354 if (pos == "left") { 1355 axis.box = { left: plotOffset.left + axisMargin, width: lw }; 1356 plotOffset.left += lw + axisMargin; 1357 } 1358 else { 1359 plotOffset.right += lw + axisMargin; 1360 axis.box = { left: surface.width - plotOffset.right, width: lw }; 1361 } 1362 } 1363 1364 // save for future reference 1365 axis.position = pos; 1366 axis.tickLength = tickLength; 1367 axis.box.padding = padding; 1368 axis.innermost = innermost; 1369 } 1370 1371 function allocateAxisBoxSecondPhase(axis) { 1372 // now that all axis boxes have been placed in one 1373 // dimension, we can set the remaining dimension coordinates 1374 if (axis.direction == "x") { 1375 axis.box.left = plotOffset.left - axis.labelWidth / 2; 1376 axis.box.width = surface.width - plotOffset.left - plotOffset.right + axis.labelWidth; 1377 } 1378 else { 1379 axis.box.top = plotOffset.top - axis.labelHeight / 2;
1380 axis.box.height = surface.height - plotOffset.bottom - plotOffset.top + axis.labelHeight; 1381 } 1382 } 1383 1384 function adjustLayoutForThingsStickingOut() { 1385 // possibly adjust plot offset to ensure everything stays 1386 // inside the canvas and isn't clipped off 1387 1388 var minMargin = options.grid.minBorderMargin, 1389 margins = { x: 0, y: 0 }, i, axis; 1390 1391 // check stuff from the plot (FIXME: this should just read 1392 // a value from the series, otherwise it's impossible to 1393 // customize) 1394 if (minMargin == null) { 1395 minMargin = 0; 1396 for (i = 0; i < series.length; ++i) 1397 minMargin = Math.max(minMargin, 2 * (series[i].points.radius + series[i].points.lineWidth/2)); 1398 } 1399 1400 margins.x = margins.y = Math.ceil(minMargin); 1401 1402 // check axis labels, note we don't check the actual 1403 // labels but instead use the overall width/height to not 1404 // jump as much around with replots 1405 $.each(allAxes(), function (_, axis) { 1406 var dir = axis.direction; 1407 if (axis.reserveSpace) 1408 margins[dir] = Math.ceil(Math.max(margins[dir], (dir == "x" ? axis.labelWidth : axis.labelHeight) / 2)); 1409 }); 1410 1411 plotOffset.left = Math.max(margins.x, plotOffset.left); 1412 plotOffset.right = Math.max(margins.x, plotOffset.right); 1413 plotOffset.top = Math.max(margins.y, plotOffset.top); 1414 plotOffset.bottom = Math.max(margins.y, plotOffset.bottom); 1415 } 1416 1417 function setupGrid() { 1418 var i, axes = allAxes(), showGrid = options.grid.show; 1419 1420 // Initialize the plot's offset from the edge of the canvas 1421 1422 for (var a in plotOffset) { 1423 var margin = options.grid.margin || 0; 1424 plotOffset[a] = typeof margin == "number" ? margin : margin[a] || 0; 1425 } 1426 1427 executeHooks(hooks.processOffset, [plotOffset]); 1428 1429 // If the grid is visible, add its border width to the offset 1430 1431 for (var a in plotOffset) { 1432 if(typeof(options.grid.borderWidth) == "object") { 1433 plotOffset[a] += showGrid ? options.grid.borderWidth[a] : 0; 1434 } 1435 else { 1436 plotOffset[a] += showGrid ? options.grid.borderWidth : 0; 1437 } 1438 } 1439 1440 // init axes 1441 $.each(axes, function (_, axis) { 1442 axis.show = axis.options.show; 1443 if (axis.show == null) 1444 axis.show = axis.used; // by default an axis is visible if it's got data 1445 1446 axis.reserveSpace = axis.show || axis.options.reserveSpace; 1447 1448 setRange(axis); 1449 }); 1450 1451 if (showGrid) { 1452 1453 var allocatedAxes = $.grep(axes, function (axis) { return axis.reserveSpace;
vendor: 4,656 bytes, lines 1453-1582
1453 }); 1454 1455 $.each(allocatedAxes, function (_, axis) { 1456 // make the ticks 1457 setupTickGeneration(axis); 1458 setTicks(axis); 1459 snapRangeToTicks(axis, axis.ticks); 1460 // find labelWidth/Height for axis 1461 measureTickLabels(axis); 1462 }); 1463 1464 // with all dimensions calculated, we can compute the 1465 // axis bounding boxes, start from the outside 1466 // (reverse order) 1467 for (i = allocatedAxes.length - 1; i >= 0; --i) 1468 allocateAxisBoxFirstPhase(allocatedAxes[i]); 1469 1470 // make sure we've got enough space for things that 1471 // might stick out 1472 adjustLayoutForThingsStickingOut(); 1473 1474 $.each(allocatedAxes, function (_, axis) { 1475 allocateAxisBoxSecondPhase(axis); 1476 }); 1477 } 1478 1479 plotWidth = surface.width - plotOffset.left - plotOffset.right; 1480 plotHeight = surface.height - plotOffset.bottom - plotOffset.top; 1481 1482 // now we got the proper plot dimensions, we can compute the scaling 1483 $.each(axes, function (_, axis) { 1484 setTransformationHelpers(axis); 1485 }); 1486 1487 if (showGrid) { 1488 drawAxisLabels(); 1489 } 1490 1491 insertLegend(); 1492 } 1493 1494 function setRange(axis) { 1495 var opts = axis.options, 1496 min = +(opts.min != null ? opts.min : axis.datamin), 1497 max = +(opts.max != null ? opts.max : axis.datamax), 1498 delta = max - min; 1499 1500 if (delta == 0.0) { 1501 // degenerate case 1502 var widen = max == 0 ? 1 : 0.01; 1503 1504 if (opts.min == null) 1505 min -= widen; 1506 // always widen max if we couldn't widen min to ensure we 1507 // don't fall into min == max which doesn't work 1508 if (opts.max == null || opts.min != null) 1509 max += widen; 1510 } 1511 else { 1512 // consider autoscaling 1513 var margin = opts.autoscaleMargin; 1514 if (margin != null) { 1515 if (opts.min == null) { 1516 min -= delta * margin; 1517 // make sure we don't go below zero if all values 1518 // are positive 1519 if (min < 0 && axis.datamin != null && axis.datamin >= 0) 1520 min = 0; 1521 } 1522 if (opts.max == null) { 1523 max += delta * margin; 1524 if (max > 0 && axis.datamax != null && axis.datamax <= 0) 1525 max = 0; 1526 } 1527 } 1528 } 1529 axis.min = min; 1530 axis.max = max; 1531 } 1532 1533 function setupTickGeneration(axis) { 1534 var opts = axis.options; 1535 1536 // estimate number of ticks 1537 var noTicks; 1538 if (typeof opts.ticks == "number" && opts.ticks > 0) 1539 noTicks = opts.ticks; 1540 else 1541 // heuristic based on the model a*sqrt(x) fitted to 1542 // some data points that seemed reasonable 1543 noTicks = 0.3 * Math.sqrt(axis.direction == "x" ? surface.width : surface.height); 1544 1545 var delta = (axis.max - axis.min) / noTicks, 1546 dec = -Math.floor(Math.log(delta) / Math.LN10), 1547 maxDec = opts.tickDecimals; 1548 1549 if (maxDec != null && dec > maxDec) { 1550 dec = maxDec; 1551 } 1552 1553 var magn = Math.pow(10, -dec), 1554 norm = delta / magn, // norm is between 1.0 and 10.0 1555 size; 1556 1557 if (norm < 1.5) { 1558 size = 1; 1559 } else if (norm < 3) { 1560 size = 2; 1561 // special case for 2.5, requires an extra decimal 1562 if (norm > 2.25 && (maxDec == null || dec + 1 <= maxDec)) { 1563 size = 2.5; 1564 ++dec; 1565 } 1566 } else if (norm < 7.5) { 1567 size = 5; 1568 } else { 1569 size = 10; 1570 } 1571 1572 size *= magn; 1573 1574 if (opts.minTickSize != null && size < opts.minTickSize) { 1575 size = opts.minTickSize; 1576 } 1577 1578 axis.delta = delta; 1579 axis.tickDecimals = Math.max(0, maxDec != null ? maxDec : dec); 1580 axis.tickSize = opts.tickSize || size; 1581 1582 // Time mode was moved to a plug-in in 0.8,
vendor: 18,417 bytes, lines 1582-2059
1582but since so many people use this 1583 // we'll add an especially friendly make sure they remembered to include it. 1584 1585 if (opts.mode == "time" && !axis.tickGenerator) { 1586 throw new Error("Time mode requires the flot.time plugin."); 1587 } 1588 1589 // Flot supports base-10 axes; any other mode else is handled by a plug-in, 1590 // like flot.time.js. 1591 1592 if (!axis.tickGenerator) { 1593 1594 axis.tickGenerator = function (axis) { 1595 1596 var ticks = [], 1597 start = floorInBase(axis.min, axis.tickSize), 1598 i = 0, 1599 v = Number.NaN, 1600 prev; 1601 1602 do { 1603 prev = v; 1604 v = start + i * axis.tickSize; 1605 ticks.push(v); 1606 ++i; 1607 } while (v < axis.max && v != prev); 1608 return ticks; 1609 }; 1610 1611 axis.tickFormatter = function (value, axis) { 1612 1613 var factor = axis.tickDecimals ? Math.pow(10, axis.tickDecimals) : 1; 1614 var formatted = "" + Math.round(value * factor) / factor; 1615 1616 // If tickDecimals was specified, ensure that we have exactly that 1617 // much precision; otherwise default to the value's own precision. 1618 1619 if (axis.tickDecimals != null) { 1620 var decimal = formatted.indexOf("."); 1621 var precision = decimal == -1 ? 0 : formatted.length - decimal - 1; 1622 if (precision < axis.tickDecimals) { 1623 return (precision ? formatted : formatted + ".") + ("" + factor).substr(1, axis.tickDecimals - precision); 1624 } 1625 } 1626 1627 return formatted; 1628 }; 1629 } 1630 1631 if ($.isFunction(opts.tickFormatter)) 1632 axis.tickFormatter = function (v, axis) { return "" + opts.tickFormatter(v, axis); }; 1633 1634 if (opts.alignTicksWithAxis != null) { 1635 var otherAxis = (axis.direction == "x" ? xaxes : yaxes)[opts.alignTicksWithAxis - 1]; 1636 if (otherAxis && otherAxis.used && otherAxis != axis) { 1637 // consider snapping min/max to outermost nice ticks 1638 var niceTicks = axis.tickGenerator(axis); 1639 if (niceTicks.length > 0) { 1640 if (opts.min == null) 1641 axis.min = Math.min(axis.min, niceTicks[0]); 1642 if (opts.max == null && niceTicks.length > 1) 1643 axis.max = Math.max(axis.max, niceTicks[niceTicks.length - 1]); 1644 } 1645 1646 axis.tickGenerator = function (axis) { 1647 // copy ticks, scaled to this axis 1648 var ticks = [], v, i; 1649 for (i = 0; i < otherAxis.ticks.length; ++i) { 1650 v = (otherAxis.ticks[i].v - otherAxis.min) / (otherAxis.max - otherAxis.min); 1651 v = axis.min + v * (axis.max - axis.min); 1652 ticks.push(v); 1653 } 1654 return ticks; 1655 }; 1656 1657 // we might need an extra decimal since forced 1658 // ticks don't necessarily fit naturally 1659 if (!axis.mode && opts.tickDecimals == null) { 1660 var extraDec = Math.max(0, -Math.floor(Math.log(axis.delta) / Math.LN10) + 1), 1661 ts = axis.tickGenerator(axis); 1662 1663 // only proceed if the tick interval rounded 1664 // with an extra decimal doesn't give us a 1665 // zero at end 1666 if (!(ts.length > 1 && /\..*0$/.test((ts[1] - ts[0]).toFixed(extraDec)))) 1667 axis.tickDecimals = extraDec; 1668 } 1669 } 1670 } 1671 } 1672 1673 function setTicks(axis) { 1674 var oticks = axis.options.ticks, ticks = []; 1675 if (oticks == null || (typeof oticks == "number" && oticks > 0)) 1676 ticks = axis.tickGenerator(axis); 1677 else if (oticks) { 1678 if ($.isFunction(oticks)) 1679 // generate the ticks 1680 ticks = oticks(axis); 1681 else 1682 ticks = oticks; 1683 } 1684 1685 // clean up/labelify the supplied ticks, copy them over 1686 var i, v; 1687 axis.ticks = []; 1688 for (i = 0; i < ticks.length; ++i) { 1689 var label = null; 1690 var t = ticks[i]; 1691 if (typeof t == "object") { 1692 v = +t[0]; 1693 if (t.length > 1) 1694 label = t[1]; 1695 } 1696 else 1697 v = +t; 1698 if (label == null) 1699 label = axis.tickFormatter(v, axis); 1700 if (!isNaN(v)) 1701 axis.ticks.push({ v: v, label: label }); 1702 } 1703 } 1704 1705 function snapRangeToTicks(axis, ticks) { 1706 if (axis.options.autoscaleMargin && ticks.length > 0) { 1707 // snap to ticks 1708 if (axis.options.min == null) 1709 axis.min = Math.min(axis.min, ticks[0].v); 1710 if (axis.options.max == null && ticks.length > 1) 1711 axis.max = Math.max(axis.max, ticks[ticks.length - 1].v); 1712 } 1713 } 1714 1715 function draw() { 1716 1717 surface.clear(); 1718 1719 executeHooks(hooks.drawBackground, [ctx]); 1720 1721 var grid = options.grid; 1722 1723 // draw background, if any 1724 if (grid.show && grid.backgroundColor) 1725 drawBackground(); 1726 1727 if (grid.show && !grid.aboveData) { 1728 drawGrid(); 1729 } 1730 1731 for (var i = 0; i < series.length; ++i) { 1732 executeHooks(hooks.drawSeries, [ctx, series[i]]); 1733 drawSeries(series[i]); 1734 } 1735 1736 executeHooks(hooks.draw, [ctx]); 1737 1738 if (grid.show && grid.aboveData) { 1739 drawGrid(); 1740 } 1741 1742 surface.render(); 1743 } 1744 1745 function extractRange(ranges, coord) { 1746 var axis, from, to, key, axes = allAxes(); 1747 1748 for (var i = 0; i < axes.length; ++i) { 1749 axis = axes[i]; 1750 if (axis.direction == coord) { 1751 key = coord + axis.n + "axis"; 1752 if (!ranges[key] && axis.n == 1) 1753 key = coord + "axis"; // support x1axis as xaxis 1754 if (ranges[key]) { 1755 from = ranges[key].from; 1756 to = ranges[key].to; 1757 break; 1758 } 1759 } 1760 } 1761 1762 // backwards-compat stuff - to be removed in future 1763 if (!ranges[key]) { 1764 axis = coord == "x" ? xaxes[0] : yaxes[0]; 1765 from = ranges[coord + "1"]; 1766 to = ranges[coord + "2"]; 1767 } 1768 1769 // auto-reverse as an added bonus 1770 if (from != null && to != null && from > to) { 1771 var tmp = from; 1772 from = to; 1773 to = tmp; 1774 } 1775 1776 return { from: from, to: to, axis: axis }; 1777 } 1778 1779 function drawBackground() { 1780 ctx.save(); 1781 ctx.translate(plotOffset.left, plotOffset.top); 1782 1783 ctx.fillStyle = getColorOrGradient(options.grid.backgroundColor, plotHeight, 0, "rgba(255, 255, 255, 0)"); 1784 ctx.fillRect(0, 0, plotWidth, plotHeight); 1785 ctx.restore(); 1786 } 1787 1788 function drawGrid() { 1789 var i, axes, bw, bc; 1790 1791 ctx.save(); 1792 ctx.translate(plotOffset.left, plotOffset.top); 1793 1794 // draw markings 1795 var markings = options.grid.markings; 1796 if (markings) { 1797 if ($.isFunction(markings)) { 1798 axes = plot.getAxes(); 1799 // xmin etc. is backwards compatibility, to be 1800 // removed in the future 1801 axes.xmin = axes.xaxis.min; 1802 axes.xmax = axes.xaxis.max; 1803 axes.ymin = axes.yaxis.min; 1804 axes.ymax = axes.yaxis.max; 1805 1806 markings = markings(axes); 1807 } 1808 1809 for (i = 0; i < markings.length; ++i) { 1810 var m = markings[i], 1811 xrange = extractRange(m, "x"), 1812 yrange = extractRange(m, "y"); 1813 1814 // fill in missing 1815 if (xrange.from == null) 1816 xrange.from = xrange.axis.min; 1817 if (xrange.to == null) 1818 xrange.to = xrange.axis.max; 1819 if (yrange.from == null) 1820 yrange.from = yrange.axis.min; 1821 if (yrange.to == null) 1822 yrange.to = yrange.axis.max; 1823 1824 // clip 1825 if (xrange.to < xrange.axis.min || xrange.from > xrange.axis.max || 1826 yrange.to < yrange.axis.min || yrange.from > yrange.axis.max) 1827 continue; 1828 1829 xrange.from = Math.max(xrange.from, xrange.axis.min); 1830 xrange.to = Math.min(xrange.to, xrange.axis.max); 1831 yrange.from = Math.max(yrange.from, yrange.axis.min); 1832 yrange.to = Math.min(yrange.to, yrange.axis.max); 1833 1834 if (xrange.from == xrange.to && yrange.from == yrange.to) 1835 continue; 1836 1837 // then draw 1838 xrange.from = xrange.axis.p2c(xrange.from); 1839 xrange.to = xrange.axis.p2c(xrange.to); 1840 yrange.from = yrange.axis.p2c(yrange.from); 1841 yrange.to = yrange.axis.p2c(yrange.to); 1842 1843 if (xrange.from == xrange.to || yrange.from == yrange.to) { 1844 // draw line 1845 ctx.beginPath(); 1846 ctx.strokeStyle = m.color || options.grid.markingsColor; 1847 ctx.lineWidth = m.lineWidth || options.grid.markingsLineWidth; 1848 ctx.moveTo(xrange.from, yrange.from); 1849 ctx.lineTo(xrange.to, yrange.to); 1850 ctx.stroke(); 1851 } 1852 else { 1853 // fill area 1854 ctx.fillStyle = m.color || options.grid.markingsColor; 1855 ctx.fillRect(xrange.from, yrange.to, 1856 xrange.to - xrange.from, 1857 yrange.from - yrange.to); 1858 } 1859 } 1860 } 1861 1862 // draw the ticks 1863 axes = allAxes(); 1864 bw = options.grid.borderWidth; 1865 1866 for (var j = 0; j < axes.length; ++j) { 1867 var axis = axes[j], box = axis.box, 1868 t = axis.tickLength, x, y, xoff, yoff; 1869 if (!axis.show || axis.ticks.length == 0) 1870 continue; 1871 1872 ctx.lineWidth = 1; 1873 1874 // find the edges 1875 if (axis.direction == "x") { 1876 x = 0; 1877 if (t == "full") 1878 y = (axis.position == "top" ? 0 : plotHeight); 1879 else 1880 y = box.top - plotOffset.top + (axis.position == "top" ? box.height : 0); 1881 } 1882 else { 1883 y = 0; 1884 if (t == "full") 1885 x = (axis.position == "left" ? 0 : plotWidth); 1886 else 1887 x = box.left - plotOffset.left + (axis.position == "left" ? box.width : 0); 1888 } 1889 1890 // draw tick bar 1891 if (!axis.innermost) { 1892 ctx.strokeStyle = axis.options.color; 1893 ctx.beginPath(); 1894 xoff = yoff = 0; 1895 if (axis.direction == "x") 1896 xoff = plotWidth + 1; 1897 else 1898 yoff = plotHeight + 1; 1899 1900 if (ctx.lineWidth == 1) { 1901 if (axis.direction == "x") { 1902 y = Math.floor(y) + 0.5; 1903 } else { 1904 x = Math.floor(x) + 0.5; 1905 } 1906 } 1907 1908 ctx.moveTo(x, y); 1909 ctx.lineTo(x + xoff, y + yoff); 1910 ctx.stroke(); 1911 } 1912 1913 // draw ticks 1914 1915 ctx.strokeStyle = axis.options.tickColor; 1916 1917 ctx.beginPath(); 1918 for (i = 0; i < axis.ticks.length; ++i) { 1919 var v = axis.ticks[i].v; 1920 1921 xoff = yoff = 0; 1922 1923 if (isNaN(v) || v < axis.min || v > axis.max 1924 // skip those lying on the axes if we got a border 1925 || (t == "full" 1926 && ((typeof bw == "object" && bw[axis.position] > 0) || bw > 0) 1927 && (v == axis.min || v == axis.max))) 1928 continue; 1929 1930 if (axis.direction == "x") { 1931 x = axis.p2c(v); 1932 yoff = t == "full" ? -plotHeight : t; 1933 1934 if (axis.position == "top") 1935 yoff = -yoff; 1936 } 1937 else { 1938 y = axis.p2c(v); 1939 xoff = t == "full" ? -plotWidth : t; 1940 1941 if (axis.position == "left") 1942 xoff = -xoff; 1943 } 1944 1945 if (ctx.lineWidth == 1) { 1946 if (axis.direction == "x") 1947 x = Math.floor(x) + 0.5; 1948 else 1949 y = Math.floor(y) + 0.5; 1950 } 1951 1952 ctx.moveTo(x, y); 1953 ctx.lineTo(x + xoff, y + yoff); 1954 } 1955 1956 ctx.stroke(); 1957 } 1958 1959 1960 // draw border 1961 if (bw) { 1962 // If either borderWidth or borderColor is an object, then draw the border 1963 // line by line instead of as one rectangle 1964 bc = options.grid.borderColor; 1965 if(typeof bw == "object" || typeof bc == "object") { 1966 if (typeof bw !== "object") { 1967 bw = {top: bw, right: bw, bottom: bw, left: bw}; 1968 } 1969 if (typeof bc !== "object") { 1970 bc = {top: bc, right: bc, bottom: bc, left: bc}; 1971 } 1972 1973 if (bw.top > 0) { 1974 ctx.strokeStyle = bc.top; 1975 ctx.lineWidth = bw.top; 1976 ctx.beginPath(); 1977 ctx.moveTo(0 - bw.left, 0 - bw.top/2); 1978 ctx.lineTo(plotWidth, 0 - bw.top/2); 1979 ctx.stroke(); 1980 } 1981 1982 if (bw.right > 0) { 1983 ctx.strokeStyle = bc.right; 1984 ctx.lineWidth = bw.right; 1985 ctx.beginPath(); 1986 ctx.moveTo(plotWidth + bw.right / 2, 0 - bw.top); 1987 ctx.lineTo(plotWidth + bw.right / 2, plotHeight); 1988 ctx.stroke(); 1989 } 1990 1991 if (bw.bottom > 0) { 1992 ctx.strokeStyle = bc.bottom; 1993 ctx.lineWidth = bw.bottom; 1994 ctx.beginPath(); 1995 ctx.moveTo(plotWidth + bw.right, plotHeight + bw.bottom / 2); 1996 ctx.lineTo(0, plotHeight + bw.bottom / 2); 1997 ctx.stroke(); 1998 } 1999 2000 if (bw.left > 0) { 2001 ctx.strokeStyle = bc.left; 2002 ctx.lineWidth = bw.left; 2003 ctx.beginPath(); 2004 ctx.moveTo(0 - bw.left/2, plotHeight + bw.bottom); 2005 ctx.lineTo(0- bw.left/2, 0); 2006 ctx.stroke(); 2007 } 2008 } 2009 else { 2010 ctx.lineWidth = bw; 2011 ctx.strokeStyle = options.grid.borderColor; 2012 ctx.strokeRect(-bw/2, -bw/2, plotWidth + bw, plotHeight + bw); 2013 } 2014 } 2015 2016 ctx.restore(); 2017 } 2018 2019 function drawAxisLabels() { 2020 2021 $.each(allAxes(), function (_, axis) { 2022 if (!axis.show || axis.ticks.length == 0) 2023 return; 2024 2025 var box = axis.box, 2026 legacyStyles = axis.direction + "Axis " + axis.direction + axis.n + "Axis", 2027 layer = "flot-" + axis.direction + "-axis flot-" + axis.direction + axis.n + "-axis " + legacyStyles, 2028 font = axis.options.font || "flot-tick-label tickLabel", 2029 tick, x, y, halign, valign; 2030 2031 surface.removeText(layer); 2032 2033 for (var i = 0; i < axis.ticks.length; ++i) { 2034 2035 tick = axis.ticks[i]; 2036 if (!tick.label || tick.v < axis.min || tick.v > axis.max) 2037 continue; 2038 2039 if (axis.direction == "x") { 2040 halign = "center"; 2041 x = plotOffset.left + axis.p2c(tick.v); 2042 if (axis.position == "bottom") { 2043 y = box.top + box.padding; 2044 } else { 2045 y = box.top + box.height - box.padding; 2046 valign = "bottom"; 2047 } 2048 } else { 2049 valign = "middle"; 2050 y = plotOffset.top + axis.p2c(tick.v); 2051 if (axis.position == "left") { 2052 x = box.left + box.width - box.padding; 2053 halign = "right"; 2054 } else { 2055 x = box.left + box.padding; 2056 } 2057 } 2058 2059 surface.addText(layer, x, y, tick.label, font
vendor: 12,640 bytes, lines 2059-2361
2059, null, halign, valign); 2060 } 2061 }); 2062 } 2063 2064 function drawSeries(series) { 2065 if (series.lines.show) 2066 drawSeriesLines(series); 2067 if (series.bars.show) 2068 drawSeriesBars(series); 2069 if (series.points.show) 2070 drawSeriesPoints(series); 2071 } 2072 2073 function drawSeriesLines(series) { 2074 function plotLine(datapoints, xoffset, yoffset, axisx, axisy) { 2075 var points = datapoints.points, 2076 ps = datapoints.pointsize, 2077 prevx = null, prevy = null; 2078 2079 ctx.beginPath(); 2080 for (var i = ps; i < points.length; i += ps) { 2081 var x1 = points[i - ps], y1 = points[i - ps + 1], 2082 x2 = points[i], y2 = points[i + 1]; 2083 2084 if (x1 == null || x2 == null) 2085 continue; 2086 2087 // clip with ymin 2088 if (y1 <= y2 && y1 < axisy.min) { 2089 if (y2 < axisy.min) 2090 continue; // line segment is outside 2091 // compute new intersection point 2092 x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1; 2093 y1 = axisy.min; 2094 } 2095 else if (y2 <= y1 && y2 < axisy.min) { 2096 if (y1 < axisy.min) 2097 continue; 2098 x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1; 2099 y2 = axisy.min; 2100 } 2101 2102 // clip with ymax 2103 if (y1 >= y2 && y1 > axisy.max) { 2104 if (y2 > axisy.max) 2105 continue; 2106 x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1; 2107 y1 = axisy.max; 2108 } 2109 else if (y2 >= y1 && y2 > axisy.max) { 2110 if (y1 > axisy.max) 2111 continue; 2112 x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1; 2113 y2 = axisy.max; 2114 } 2115 2116 // clip with xmin 2117 if (x1 <= x2 && x1 < axisx.min) { 2118 if (x2 < axisx.min) 2119 continue; 2120 y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1; 2121 x1 = axisx.min; 2122 } 2123 else if (x2 <= x1 && x2 < axisx.min) { 2124 if (x1 < axisx.min) 2125 continue; 2126 y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1; 2127 x2 = axisx.min; 2128 } 2129 2130 // clip with xmax 2131 if (x1 >= x2 && x1 > axisx.max) { 2132 if (x2 > axisx.max) 2133 continue; 2134 y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1; 2135 x1 = axisx.max; 2136 } 2137 else if (x2 >= x1 && x2 > axisx.max) { 2138 if (x1 > axisx.max) 2139 continue; 2140 y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1; 2141 x2 = axisx.max; 2142 } 2143 2144 if (x1 != prevx || y1 != prevy) 2145 ctx.moveTo(axisx.p2c(x1) + xoffset, axisy.p2c(y1) + yoffset); 2146 2147 prevx = x2; 2148 prevy = y2; 2149 ctx.lineTo(axisx.p2c(x2) + xoffset, axisy.p2c(y2) + yoffset); 2150 } 2151 ctx.stroke(); 2152 } 2153 2154 function plotLineArea(datapoints, axisx, axisy) { 2155 var points = datapoints.points, 2156 ps = datapoints.pointsize, 2157 bottom = Math.min(Math.max(0, axisy.min), axisy.max), 2158 i = 0, top, areaOpen = false, 2159 ypos = 1, segmentStart = 0, segmentEnd = 0; 2160 2161 // we process each segment in two turns, first forward 2162 // direction to sketch out top, then once we hit the 2163 // end we go backwards to sketch the bottom 2164 while (true) { 2165 if (ps > 0 && i > points.length + ps) 2166 break; 2167 2168 i += ps; // ps is negative if going backwards 2169 2170 var x1 = points[i - ps], 2171 y1 = points[i - ps + ypos], 2172 x2 = points[i], y2 = points[i + ypos]; 2173 2174 if (areaOpen) { 2175 if (ps > 0 && x1 != null && x2 == null) { 2176 // at turning point 2177 segmentEnd = i; 2178 ps = -ps; 2179 ypos = 2; 2180 continue; 2181 } 2182 2183 if (ps < 0 && i == segmentStart + ps) { 2184 // done with the reverse sweep 2185 ctx.fill(); 2186 areaOpen = false; 2187 ps = -ps; 2188 ypos = 1; 2189 i = segmentStart = segmentEnd + ps; 2190 continue; 2191 } 2192 } 2193 2194 if (x1 == null || x2 == null) 2195 continue; 2196 2197 // clip x values 2198 2199 // clip with xmin 2200 if (x1 <= x2 && x1 < axisx.min) { 2201 if (x2 < axisx.min) 2202 continue; 2203 y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1; 2204 x1 = axisx.min; 2205 } 2206 else if (x2 <= x1 && x2 < axisx.min) { 2207 if (x1 < axisx.min) 2208 continue; 2209 y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1; 2210 x2 = axisx.min; 2211 } 2212 2213 // clip with xmax 2214 if (x1 >= x2 && x1 > axisx.max) { 2215 if (x2 > axisx.max) 2216 continue; 2217 y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1; 2218 x1 = axisx.max; 2219 } 2220 else if (x2 >= x1 && x2 > axisx.max) { 2221 if (x1 > axisx.max) 2222 continue; 2223 y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1; 2224 x2 = axisx.max; 2225 } 2226 2227 if (!areaOpen) { 2228 // open area 2229 ctx.beginPath(); 2230 ctx.moveTo(axisx.p2c(x1), axisy.p2c(bottom)); 2231 areaOpen = true; 2232 } 2233 2234 // now first check the case where both is outside 2235 if (y1 >= axisy.max && y2 >= axisy.max) { 2236 ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.max)); 2237 ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.max)); 2238 continue; 2239 } 2240 else if (y1 <= axisy.min && y2 <= axisy.min) { 2241 ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.min)); 2242 ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.min)); 2243 continue; 2244 } 2245 2246 // else it's a bit more complicated, there might 2247 // be a flat maxed out rectangle first, then a 2248 // triangular cutout or reverse; to find these 2249 // keep track of the current x values 2250 var x1old = x1, x2old = x2; 2251 2252 // clip the y values, without shortcutting, we 2253 // go through all cases in turn 2254 2255 // clip with ymin 2256 if (y1 <= y2 && y1 < axisy.min && y2 >= axisy.min) { 2257 x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1; 2258 y1 = axisy.min; 2259 } 2260 else if (y2 <= y1 && y2 < axisy.min && y1 >= axisy.min) { 2261 x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1; 2262 y2 = axisy.min; 2263 } 2264 2265 // clip with ymax 2266 if (y1 >= y2 && y1 > axisy.max && y2 <= axisy.max) { 2267 x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1; 2268 y1 = axisy.max; 2269 } 2270 else if (y2 >= y1 && y2 > axisy.max && y1 <= axisy.max) { 2271 x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1; 2272 y2 = axisy.max; 2273 } 2274 2275 // if the x value was changed we got a rectangle 2276 // to fill 2277 if (x1 != x1old) { 2278 ctx.lineTo(axisx.p2c(x1old), axisy.p2c(y1)); 2279 // it goes to (x1, y1), but we fill that below 2280 } 2281 2282 // fill triangular section, this sometimes result 2283 // in redundant points if (x1, y1) hasn't changed 2284 // from previous line to, but we just ignore that 2285 ctx.lineTo(axisx.p2c(x1), axisy.p2c(y1)); 2286 ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2)); 2287 2288 // fill the other rectangle if it's there 2289 if (x2 != x2old) { 2290 ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2)); 2291 ctx.lineTo(axisx.p2c(x2old), axisy.p2c(y2)); 2292 } 2293 } 2294 } 2295 2296 ctx.save(); 2297 ctx.translate(plotOffset.left, plotOffset.top); 2298 ctx.lineJoin = "round"; 2299 2300 var lw = series.lines.lineWidth, 2301 sw = series.shadowSize; 2302 // FIXME: consider another form of shadow when filling is turned on 2303 if (lw > 0 && sw > 0) { 2304 // draw shadow as a thick and thin line with transparency 2305 ctx.lineWidth = sw; 2306 ctx.strokeStyle = "rgba(0,0,0,0.1)"; 2307 // position shadow at angle from the mid of line 2308 var angle = Math.PI/18; 2309 plotLine(series.datapoints, Math.sin(angle) * (lw/2 + sw/2), Math.cos(angle) * (lw/2 + sw/2), series.xaxis, series.yaxis); 2310 ctx.lineWidth = sw/2; 2311 plotLine(series.datapoints, Math.sin(angle) * (lw/2 + sw/4), Math.cos(angle) * (lw/2 + sw/4), series.xaxis, series.yaxis); 2312 } 2313 2314 ctx.lineWidth = lw; 2315 ctx.strokeStyle = series.color; 2316 var fillStyle = getFillStyle(series.lines, series.color, 0, plotHeight); 2317 if (fillStyle) { 2318 ctx.fillStyle = fillStyle; 2319 plotLineArea(series.datapoints, series.xaxis, series.yaxis); 2320 } 2321 2322 if (lw > 0) 2323 plotLine(series.datapoints, 0, 0, series.xaxis, series.yaxis); 2324 ctx.restore(); 2325 } 2326 2327 function drawSeriesPoints(series) { 2328 function plotPoints(datapoints, radius, fillStyle, offset, shadow, axisx, axisy, symbol) { 2329 var points = datapoints.points, ps = datapoints.pointsize; 2330 2331 for (var i = 0; i < points.length; i += ps) { 2332 var x = points[i], y = points[i + 1]; 2333 if (x == null || x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max) 2334 continue; 2335 2336 ctx.beginPath(); 2337 x = axisx.p2c(x); 2338 y = axisy.p2c(y) + offset; 2339 if (symbol == "circle") 2340 ctx.arc(x, y, radius, 0, shadow ? Math.PI : Math.PI * 2, false); 2341 else 2342 symbol(ctx, x, y, radius, shadow); 2343 ctx.closePath(); 2344 2345 if (fillStyle) { 2346 ctx.fillStyle = fillStyle; 2347 ctx.fill(); 2348 } 2349 ctx.stroke(); 2350 } 2351 } 2352 2353 ctx.save(); 2354 ctx.translate(plotOffset.left, plotOffset.top); 2355 2356 var lw = series.points.lineWidth, 2357 sw = series.shadowSize, 2358 radius = series.points.radius, 2359 symbol = series.points.symbol; 2360 2361 // If the user sets the line width to 0, we change it to a very
vendor: 6,962 bytes, lines 2361-2558
2361 2362 // small value. A line width of 0 seems to force the default of 1. 2363 // Doing the conditional here allows the shadow setting to still be 2364 // optional even with a lineWidth of 0. 2365 2366 if( lw == 0 ) 2367 lw = 0.0001; 2368 2369 if (lw > 0 && sw > 0) { 2370 // draw shadow in two steps 2371 var w = sw / 2; 2372 ctx.lineWidth = w; 2373 ctx.strokeStyle = "rgba(0,0,0,0.1)"; 2374 plotPoints(series.datapoints, radius, null, w + w/2, true, 2375 series.xaxis, series.yaxis, symbol); 2376 2377 ctx.strokeStyle = "rgba(0,0,0,0.2)"; 2378 plotPoints(series.datapoints, radius, null, w/2, true, 2379 series.xaxis, series.yaxis, symbol); 2380 } 2381 2382 ctx.lineWidth = lw; 2383 ctx.strokeStyle = series.color; 2384 plotPoints(series.datapoints, radius, 2385 getFillStyle(series.points, series.color), 0, false, 2386 series.xaxis, series.yaxis, symbol); 2387 ctx.restore(); 2388 } 2389 2390 function drawBar(x, y, b, barLeft, barRight, offset, fillStyleCallback, axisx, axisy, c, horizontal, lineWidth) { 2391 var left, right, bottom, top, 2392 drawLeft, drawRight, drawTop, drawBottom, 2393 tmp; 2394 2395 // in horizontal mode, we start the bar from the left 2396 // instead of from the bottom so it appears to be 2397 // horizontal rather than vertical 2398 if (horizontal) { 2399 drawBottom = drawRight = drawTop = true; 2400 drawLeft = false; 2401 left = b; 2402 right = x; 2403 top = y + barLeft; 2404 bottom = y + barRight; 2405 2406 // account for negative bars 2407 if (right < left) { 2408 tmp = right; 2409 right = left; 2410 left = tmp; 2411 drawLeft = true; 2412 drawRight = false; 2413 } 2414 } 2415 else { 2416 drawLeft = drawRight = drawTop = true; 2417 drawBottom = false; 2418 left = x + barLeft; 2419 right = x + barRight; 2420 bottom = b; 2421 top = y; 2422 2423 // account for negative bars 2424 if (top < bottom) { 2425 tmp = top; 2426 top = bottom; 2427 bottom = tmp; 2428 drawBottom = true; 2429 drawTop = false; 2430 } 2431 } 2432 2433 // clip 2434 if (right < axisx.min || left > axisx.max || 2435 top < axisy.min || bottom > axisy.max) 2436 return; 2437 2438 if (left < axisx.min) { 2439 left = axisx.min; 2440 drawLeft = false; 2441 } 2442 2443 if (right > axisx.max) { 2444 right = axisx.max; 2445 drawRight = false; 2446 } 2447 2448 if (bottom < axisy.min) { 2449 bottom = axisy.min; 2450 drawBottom = false; 2451 } 2452 2453 if (top > axisy.max) { 2454 top = axisy.max; 2455 drawTop = false; 2456 } 2457 2458 left = axisx.p2c(left); 2459 bottom = axisy.p2c(bottom); 2460 right = axisx.p2c(right); 2461 top = axisy.p2c(top); 2462 2463 // fill the bar 2464 if (fillStyleCallback) { 2465 c.beginPath(); 2466 c.moveTo(left, bottom); 2467 c.lineTo(left, top); 2468 c.lineTo(right, top); 2469 c.lineTo(right, bottom); 2470 c.fillStyle = fillStyleCallback(bottom, top); 2471 c.fill(); 2472 } 2473 2474 // draw outline 2475 if (lineWidth > 0 && (drawLeft || drawRight || drawTop || drawBottom)) { 2476 c.beginPath(); 2477 2478 // FIXME: inline moveTo is buggy with excanvas 2479 c.moveTo(left, bottom + offset); 2480 if (drawLeft) 2481 c.lineTo(left, top + offset); 2482 else 2483 c.moveTo(left, top + offset); 2484 if (drawTop) 2485 c.lineTo(right, top + offset); 2486 else 2487 c.moveTo(right, top + offset); 2488 if (drawRight) 2489 c.lineTo(right, bottom + offset); 2490 else 2491 c.moveTo(right, bottom + offset); 2492 if (drawBottom) 2493 c.lineTo(left, bottom + offset); 2494 else 2495 c.moveTo(left, bottom + offset); 2496 c.stroke(); 2497 } 2498 } 2499 2500 function drawSeriesBars(series) { 2501 function plotBars(datapoints, barLeft, barRight, offset, fillStyleCallback, axisx, axisy) { 2502 var points = datapoints.points, ps = datapoints.pointsize; 2503 2504 for (var i = 0; i < points.length; i += ps) { 2505 if (points[i] == null) 2506 continue; 2507 drawBar(points[i], points[i + 1], points[i + 2], barLeft, barRight, offset, fillStyleCallback, axisx, axisy, ctx, series.bars.horizontal, series.bars.lineWidth); 2508 } 2509 } 2510 2511 ctx.save(); 2512 ctx.translate(plotOffset.left, plotOffset.top); 2513 2514 // FIXME: figure out a way to add shadows (for instance along the right edge) 2515 ctx.lineWidth = series.bars.lineWidth; 2516 ctx.strokeStyle = series.color; 2517 2518 var barLeft; 2519 2520 switch (series.bars.align) { 2521 case "left": 2522 barLeft = 0; 2523 break; 2524 case "right": 2525 barLeft = -series.bars.barWidth; 2526 break; 2527 case "center": 2528 barLeft = -series.bars.barWidth / 2; 2529 break; 2530 default: 2531 throw new Error("Invalid bar alignment: " + series.bars.align); 2532 } 2533 2534 var fillStyleCallback = series.bars.fill ? function (bottom, top) { return getFillStyle(series.bars, series.color, bottom, top); } : null; 2535 plotBars(series.datapoints, barLeft, barLeft + series.bars.barWidth, 0, fillStyleCallback, series.xaxis, series.yaxis); 2536 ctx.restore(); 2537 } 2538 2539 function getFillStyle(filloptions, seriesColor, bottom, top) { 2540 var fill = filloptions.fill; 2541 if (!fill) 2542 return null; 2543 2544 if (filloptions.fillColor) 2545 return getColorOrGradient(filloptions.fillColor, bottom, top, seriesColor); 2546 2547 var c = $.color.parse(seriesColor); 2548 c.a = typeof fill == "number" ? fill : 0.4; 2549 c.normalize(); 2550 return c.toString(); 2551 } 2552 2553 function insertLegend() { 2554 2555 placeholder.find(".legend").remove(); 2556 2557 if (!options.legend.show) 2558 return;
vendor: 7,009 bytes, lines 2558-2718
2558 2559 2560 var fragments = [], entries = [], rowStarted = false, 2561 lf = options.legend.labelFormatter, s, label; 2562 2563 // Build a list of legend entries, with each having a label and a color 2564 2565 for (var i = 0; i < series.length; ++i) { 2566 s = series[i]; 2567 if (s.label) { 2568 label = lf ? lf(s.label, s) : s.label; 2569 if (label) { 2570 entries.push({ 2571 label: label, 2572 color: s.color 2573 }); 2574 } 2575 } 2576 } 2577 2578 // Sort the legend using either the default or a custom comparator 2579 2580 if (options.legend.sorted) { 2581 if ($.isFunction(options.legend.sorted)) { 2582 entries.sort(options.legend.sorted); 2583 } else if (options.legend.sorted == "reverse") { 2584 entries.reverse(); 2585 } else { 2586 var ascending = options.legend.sorted != "descending"; 2587 entries.sort(function(a, b) { 2588 return a.label == b.label ? 0 : ( 2589 (a.label < b.label) != ascending ? 1 : -1 // Logical XOR 2590 ); 2591 }); 2592 } 2593 } 2594 2595 // Generate markup for the list of entries, in their final order 2596 2597 for (var i = 0; i < entries.length; ++i) { 2598 2599 var entry = entries[i]; 2600 2601 if (i % options.legend.noColumns == 0) { 2602 if (rowStarted) 2603 fragments.push('</tr>'); 2604 fragments.push('<tr>'); 2605 rowStarted = true; 2606 } 2607 2608 fragments.push( 2609 '<td class="legendColorBox"><div style="border:1px solid ' + options.legend.labelBoxBorderColor + ';padding:1px"><div style="width:4px;height:0;border:5px solid ' + entry.color + ';overflow:hidden"></div></div></td>' + 2610 '<td class="legendLabel">' + entry.label + '</td>' 2611 ); 2612 } 2613 2614 if (rowStarted) 2615 fragments.push('</tr>'); 2616 2617 if (fragments.length == 0) 2618 return; 2619 2620 var table = '<table style="font-size:smaller;color:' + options.grid.color + '">' + fragments.join("") + '</table>'; 2621 if (options.legend.container != null) 2622 $(options.legend.container).html(table); 2623 else { 2624 var pos = "", 2625 p = options.legend.position, 2626 m = options.legend.margin; 2627 if (m[0] == null) 2628 m = [m, m]; 2629 if (p.charAt(0) == "n") 2630 pos += 'top:' + (m[1] + plotOffset.top) + 'px;'; 2631 else if (p.charAt(0) == "s") 2632 pos += 'bottom:' + (m[1] + plotOffset.bottom) + 'px;'; 2633 if (p.charAt(1) == "e") 2634 pos += 'right:' + (m[0] + plotOffset.right) + 'px;'; 2635 else if (p.charAt(1) == "w") 2636 pos += 'left:' + (m[0] + plotOffset.left) + 'px;'; 2637 var legend = $('<div class="legend">' + table.replace('style="', 'style="position:absolute;' + pos +';') + '</div>').appendTo(placeholder); 2638 if (options.legend.backgroundOpacity != 0.0) { 2639 // put in the transparent background 2640 // separately to avoid blended labels and 2641 // label boxes 2642 var c = options.legend.backgroundColor; 2643 if (c == null) { 2644 c = options.grid.backgroundColor; 2645 if (c && typeof c == "string") 2646 c = $.color.parse(c); 2647 else 2648 c = $.color.extract(legend, 'background-color'); 2649 c.a = 1; 2650 c = c.toString(); 2651 } 2652 var div = legend.children(); 2653 $('<div style="position:absolute;width:' + div.width() + 'px;height:' + div.height() + 'px;' + pos +'background-color:' + c + ';"> </div>').prependTo(legend).css('opacity', options.legend.backgroundOpacity); 2654 } 2655 } 2656 } 2657 2658 2659 // interactive features 2660 2661 var highlights = [], 2662 redrawTimeout = null; 2663 2664 // returns the data item the mouse is over, or null if none is found 2665 function findNearbyItem(mouseX, mouseY, seriesFilter) { 2666 var maxDistance = options.grid.mouseActiveRadius, 2667 smallestDistance = maxDistance * maxDistance + 1, 2668 item = null, foundPoint = false, i, j, ps; 2669 2670 for (i = series.length - 1; i >= 0; --i) { 2671 if (!seriesFilter(series[i])) 2672 continue; 2673 2674 var s = series[i], 2675 axisx = s.xaxis, 2676 axisy = s.yaxis, 2677 points = s.datapoints.points, 2678 mx = axisx.c2p(mouseX), // precompute some stuff to make the loop faster 2679 my = axisy.c2p(mouseY), 2680 maxx = maxDistance / axisx.scale, 2681 maxy = maxDistance / axisy.scale; 2682 2683 ps = s.datapoints.pointsize; 2684 // with inverse transforms, we can't use the maxx/maxy 2685 // optimization, sadly 2686 if (axisx.options.inverseTransform) 2687 maxx = Number.MAX_VALUE; 2688 if (axisy.options.inverseTransform) 2689 maxy = Number.MAX_VALUE; 2690 2691 if (s.lines.show || s.points.show) { 2692 for (j = 0; j < points.length; j += ps) { 2693 var x = points[j], y = points[j + 1]; 2694 if (x == null) 2695 continue; 2696 2697 // For points and lines, the cursor must be within a 2698 // certain distance to the data point 2699 if (x - mx > maxx || x - mx < -maxx || 2700 y - my > maxy || y - my < -maxy) 2701 continue; 2702 2703 // We have to calculate distances in pixels, not in 2704 // data units, because the scales of the axes may be different 2705 var dx = Math.abs(axisx.p2c(x) - mouseX), 2706 dy = Math.abs(axisy.p2c(y) - mouseY), 2707 dist = dx * dx + dy * dy; // we save the sqrt 2708 2709 // use <= to ensure last point takes precedence 2710 // (last generally means on top of) 2711 if (dist < smallestDistance) { 2712 smallestDistance = dist; 2713 item = [i, j / ps]; 2714 } 2715 } 2716 } 2717 2718 if (s.bars.show && !item) { // no other point can be nearby
vendor: 9,538 bytes, lines 2719-2980
2719 var barLeft = s.bars.align == "left" ? 0 : -s.bars.barWidth/2, 2720 barRight = barLeft + s.bars.barWidth; 2721 2722 for (j = 0; j < points.length; j += ps) { 2723 var x = points[j], y = points[j + 1], b = points[j + 2]; 2724 if (x == null) 2725 continue; 2726 2727 // for a bar graph, the cursor must be inside the bar 2728 if (series[i].bars.horizontal ? 2729 (mx <= Math.max(b, x) && mx >= Math.min(b, x) && 2730 my >= y + barLeft && my <= y + barRight) : 2731 (mx >= x + barLeft && mx <= x + barRight && 2732 my >= Math.min(b, y) && my <= Math.max(b, y))) 2733 item = [i, j / ps]; 2734 } 2735 } 2736 } 2737 2738 if (item) { 2739 i = item[0]; 2740 j = item[1]; 2741 ps = series[i].datapoints.pointsize; 2742 2743 return { datapoint: series[i].datapoints.points.slice(j * ps, (j + 1) * ps), 2744 dataIndex: j, 2745 series: series[i], 2746 seriesIndex: i }; 2747 } 2748 2749 return null; 2750 } 2751 2752 function onMouseMove(e) { 2753 if (options.grid.hoverable) 2754 triggerClickHoverEvent("plothover", e, 2755 function (s) { return s["hoverable"] != false; }); 2756 } 2757 2758 function onMouseLeave(e) { 2759 if (options.grid.hoverable) 2760 triggerClickHoverEvent("plothover", e, 2761 function (s) { return false; }); 2762 } 2763 2764 function onClick(e) { 2765 triggerClickHoverEvent("plotclick", e, 2766 function (s) { return s["clickable"] != false; }); 2767 } 2768 2769 // trigger click or hover event (they send the same parameters 2770 // so we share their code) 2771 function triggerClickHoverEvent(eventname, event, seriesFilter) { 2772 var offset = eventHolder.offset(), 2773 canvasX = event.pageX - offset.left - plotOffset.left, 2774 canvasY = event.pageY - offset.top - plotOffset.top, 2775 pos = canvasToAxisCoords({ left: canvasX, top: canvasY }); 2776 2777 pos.pageX = event.pageX; 2778 pos.pageY = event.pageY; 2779 2780 var item = findNearbyItem(canvasX, canvasY, seriesFilter); 2781 2782 if (item) { 2783 // fill in mouse pos for any listeners out there 2784 item.pageX = parseInt(item.series.xaxis.p2c(item.datapoint[0]) + offset.left + plotOffset.left, 10); 2785 item.pageY = parseInt(item.series.yaxis.p2c(item.datapoint[1]) + offset.top + plotOffset.top, 10); 2786 } 2787 2788 if (options.grid.autoHighlight) { 2789 // clear auto-highlights 2790 for (var i = 0; i < highlights.length; ++i) { 2791 var h = highlights[i]; 2792 if (h.auto == eventname && 2793 !(item && h.series == item.series && 2794 h.point[0] == item.datapoint[0] && 2795 h.point[1] == item.datapoint[1])) 2796 unhighlight(h.series, h.point); 2797 } 2798 2799 if (item) 2800 highlight(item.series, item.datapoint, eventname); 2801 } 2802 2803 placeholder.trigger(eventname, [ pos, item ]); 2804 } 2805 2806 function triggerRedrawOverlay() { 2807 var t = options.interaction.redrawOverlayInterval; 2808 if (t == -1) { // skip event queue 2809 drawOverlay(); 2810 return; 2811 } 2812 2813 if (!redrawTimeout) 2814 redrawTimeout = setTimeout(drawOverlay, t); 2815 } 2816 2817 function drawOverlay() { 2818 redrawTimeout = null; 2819 2820 // draw highlights 2821 octx.save(); 2822 overlay.clear(); 2823 octx.translate(plotOffset.left, plotOffset.top); 2824 2825 var i, hi; 2826 for (i = 0; i < highlights.length; ++i) { 2827 hi = highlights[i]; 2828 2829 if (hi.series.bars.show) 2830 drawBarHighlight(hi.series, hi.point); 2831 else 2832 drawPointHighlight(hi.series, hi.point); 2833 } 2834 octx.restore(); 2835 2836 executeHooks(hooks.drawOverlay, [octx]); 2837 } 2838 2839 function highlight(s, point, auto) { 2840 if (typeof s == "number") 2841 s = series[s]; 2842 2843 if (typeof point == "number") { 2844 var ps = s.datapoints.pointsize; 2845 point = s.datapoints.points.slice(ps * point, ps * (point + 1)); 2846 } 2847 2848 var i = indexOfHighlight(s, point); 2849 if (i == -1) { 2850 highlights.push({ series: s, point: point, auto: auto }); 2851 2852 triggerRedrawOverlay(); 2853 } 2854 else if (!auto) 2855 highlights[i].auto = false; 2856 } 2857 2858 function unhighlight(s, point) { 2859 if (s == null && point == null) { 2860 highlights = []; 2861 triggerRedrawOverlay(); 2862 return; 2863 } 2864 2865 if (typeof s == "number") 2866 s = series[s]; 2867 2868 if (typeof point == "number") { 2869 var ps = s.datapoints.pointsize; 2870 point = s.datapoints.points.slice(ps * point, ps * (point + 1)); 2871 } 2872 2873 var i = indexOfHighlight(s, point); 2874 if (i != -1) { 2875 highlights.splice(i, 1); 2876 2877 triggerRedrawOverlay(); 2878 } 2879 } 2880 2881 function indexOfHighlight(s, p) { 2882 for (var i = 0; i < highlights.length; ++i) { 2883 var h = highlights[i]; 2884 if (h.series == s && h.point[0] == p[0] 2885 && h.point[1] == p[1]) 2886 return i; 2887 } 2888 return -1; 2889 } 2890 2891 function drawPointHighlight(series, point) { 2892 var x = point[0], y = point[1], 2893 axisx = series.xaxis, axisy = series.yaxis, 2894 highlightColor = (typeof series.highlightColor === "string") ? series.highlightColor : $.color.parse(series.color).scale('a', 0.5).toString(); 2895 2896 if (x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max) 2897 return; 2898 2899 var pointRadius = series.points.radius + series.points.lineWidth / 2; 2900 octx.lineWidth = pointRadius; 2901 octx.strokeStyle = highlightColor; 2902 var radius = 1.5 * pointRadius; 2903 x = axisx.p2c(x); 2904 y = axisy.p2c(y); 2905 2906 octx.beginPath(); 2907 if (series.points.symbol == "circle") 2908 octx.arc(x, y, radius, 0, 2 * Math.PI, false); 2909 else 2910 series.points.symbol(octx, x, y, radius, false); 2911 octx.closePath(); 2912 octx.stroke(); 2913 } 2914 2915 function drawBarHighlight(series, point) { 2916 var highlightColor = (typeof series.highlightColor === "string") ? series.highlightColor : $.color.parse(series.color).scale('a', 0.5).toString(), 2917 fillStyle = highlightColor, 2918 barLeft = series.bars.align == "left" ? 0 : -series.bars.barWidth/2; 2919 2920 octx.lineWidth = series.bars.lineWidth; 2921 octx.strokeStyle = highlightColor; 2922 2923 drawBar(point[0], point[1], point[2] || 0, barLeft, barLeft + series.bars.barWidth, 2924 0, function () { return fillStyle; }, series.xaxis, series.yaxis, octx, series.bars.horizontal, series.bars.lineWidth); 2925 } 2926 2927 function getColorOrGradient(spec, bottom, top, defaultColor) { 2928 if (typeof spec == "string") 2929 return spec; 2930 else { 2931 // assume this is a gradient spec; IE currently only 2932 // supports a simple vertical gradient properly, so that's 2933 // what we support too 2934 var gradient = ctx.createLinearGradient(0, top, 0, bottom); 2935 2936 for (var i = 0, l = spec.colors.length; i < l; ++i) { 2937 var c = spec.colors[i]; 2938 if (typeof c != "string") { 2939 var co = $.color.parse(defaultColor); 2940 if (c.brightness != null) 2941 co = co.scale('rgb', c.brightness); 2942 if (c.opacity != null) 2943 co.a *= c.opacity; 2944 c = co.toString(); 2945 } 2946 gradient.addColorStop(i / (l - 1), c); 2947 } 2948 2949 return gradient; 2950 } 2951 } 2952 } 2953 2954 // Add the plot function to the top level of the jQuery object 2955 2956 $.plot = function(placeholder, data, options) { 2957 //var t0 = new Date(); 2958 var plot = new Plot($(placeholder), data, options, $.plot.plugins); 2959 //(window.console ? console.log : alert)("time used (msecs): " + ((new Date()).getTime() - t0.getTime())); 2960 return plot; 2961 }; 2962 2963 $.plot.version = "0.8.0"; 2964 2965 $.plot.plugins = []; 2966 2967 // Also add the plot function as a chainable property 2968 2969 $.fn.plot = function(data, options) { 2970 return this.each(function() { 2971 $.plot(this, data, options); 2972 }); 2973 } 2974 2975 // round to nearby lower multiple of base 2976 function floorInBase(n, base) { 2977 return base * Math.floor(n / base); 2978 } 2979 2980})(jQuery);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.