Commits

Chris Leonello committed debbda3

adding documentation directory

Comments (0)

Files changed (9)

+// Copyright 2006 Google Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+
+// Known Issues:
+//
+// * Patterns are not implemented.
+// * Radial gradient are not implemented. The VML version of these look very
+//   different from the canvas one.
+// * Clipping paths are not implemented.
+// * Coordsize. The width and height attribute have higher priority than the
+//   width and height style values which isn't correct.
+// * Painting mode isn't implemented.
+// * Canvas width/height should is using content-box by default. IE in
+//   Quirks mode will draw the canvas using border-box. Either change your
+//   doctype to HTML5
+//   (http://www.whatwg.org/specs/web-apps/current-work/#the-doctype)
+//   or use Box Sizing Behavior from WebFX
+//   (http://webfx.eae.net/dhtml/boxsizing/boxsizing.html)
+// * Non uniform scaling does not correctly scale strokes.
+// * Optimize. There is always room for speed improvements.
+
+// Only add this code if we do not already have a canvas implementation
+if (!document.createElement('canvas').getContext) {
+
+(function() {
+
+  // alias some functions to make (compiled) code shorter
+  var m = Math;
+  var mr = m.round;
+  var ms = m.sin;
+  var mc = m.cos;
+  var abs = m.abs;
+  var sqrt = m.sqrt;
+
+  // this is used for sub pixel precision
+  var Z = 10;
+  var Z2 = Z / 2;
+
+  /**
+   * This funtion is assigned to the <canvas> elements as element.getContext().
+   * @this {HTMLElement}
+   * @return {CanvasRenderingContext2D_}
+   */
+  function getContext() {
+    return this.context_ ||
+        (this.context_ = new CanvasRenderingContext2D_(this));
+  }
+
+  var slice = Array.prototype.slice;
+
+  /**
+   * Binds a function to an object. The returned function will always use the
+   * passed in {@code obj} as {@code this}.
+   *
+   * Example:
+   *
+   *   g = bind(f, obj, a, b)
+   *   g(c, d) // will do f.call(obj, a, b, c, d)
+   *
+   * @param {Function} f The function to bind the object to
+   * @param {Object} obj The object that should act as this when the function
+   *     is called
+   * @param {*} var_args Rest arguments that will be used as the initial
+   *     arguments when the function is called
+   * @return {Function} A new function that has bound this
+   */
+  function bind(f, obj, var_args) {
+    var a = slice.call(arguments, 2);
+    return function() {
+      return f.apply(obj, a.concat(slice.call(arguments)));
+    };
+  }
+
+  var G_vmlCanvasManager_ = {
+    init: function(opt_doc) {
+      if (/MSIE/.test(navigator.userAgent) && !window.opera) {
+        var doc = opt_doc || document;
+        // Create a dummy element so that IE will allow canvas elements to be
+        // recognized.
+        doc.createElement('canvas');
+        doc.attachEvent('onreadystatechange', bind(this.init_, this, doc));
+      }
+    },
+
+    init_: function(doc) {
+      // create xmlns
+      if (!doc.namespaces['g_vml_']) {
+        doc.namespaces.add('g_vml_', 'urn:schemas-microsoft-com:vml',
+                           '#default#VML');
+
+      }
+      if (!doc.namespaces['g_o_']) {
+        doc.namespaces.add('g_o_', 'urn:schemas-microsoft-com:office:office',
+                           '#default#VML');
+      }
+
+      // Setup default CSS.  Only add one style sheet per document
+      if (!doc.styleSheets['ex_canvas_']) {
+        var ss = doc.createStyleSheet();
+        ss.owningElement.id = 'ex_canvas_';
+        ss.cssText = 'canvas{display:inline-block;overflow:hidden;' +
+            // default size is 300x150 in Gecko and Opera
+            'text-align:left;width:300px;height:150px}' +
+            'g_vml_\\:*{behavior:url(#default#VML)}' +
+            'g_o_\\:*{behavior:url(#default#VML)}';
+
+      }
+
+      // find all canvas elements
+      var els = doc.getElementsByTagName('canvas');
+      for (var i = 0; i < els.length; i++) {
+        this.initElement(els[i]);
+      }
+    },
+
+    /**
+     * Public initializes a canvas element so that it can be used as canvas
+     * element from now on. This is called automatically before the page is
+     * loaded but if you are creating elements using createElement you need to
+     * make sure this is called on the element.
+     * @param {HTMLElement} el The canvas element to initialize.
+     * @return {HTMLElement} the element that was created.
+     */
+    initElement: function(el) {
+      if (!el.getContext) {
+
+        el.getContext = getContext;
+
+        // Remove fallback content. There is no way to hide text nodes so we
+        // just remove all childNodes. We could hide all elements and remove
+        // text nodes but who really cares about the fallback content.
+        el.innerHTML = '';
+
+        // do not use inline function because that will leak memory
+        el.attachEvent('onpropertychange', onPropertyChange);
+        el.attachEvent('onresize', onResize);
+
+        var attrs = el.attributes;
+        if (attrs.width && attrs.width.specified) {
+          // TODO: use runtimeStyle and coordsize
+          // el.getContext().setWidth_(attrs.width.nodeValue);
+          el.style.width = attrs.width.nodeValue + 'px';
+        } else {
+          el.width = el.clientWidth;
+        }
+        if (attrs.height && attrs.height.specified) {
+          // TODO: use runtimeStyle and coordsize
+          // el.getContext().setHeight_(attrs.height.nodeValue);
+          el.style.height = attrs.height.nodeValue + 'px';
+        } else {
+          el.height = el.clientHeight;
+        }
+        //el.getContext().setCoordsize_()
+      }
+      return el;
+    }
+  };
+
+  function onPropertyChange(e) {
+    var el = e.srcElement;
+
+    switch (e.propertyName) {
+      case 'width':
+        el.style.width = el.attributes.width.nodeValue + 'px';
+        el.getContext().clearRect();
+        break;
+      case 'height':
+        el.style.height = el.attributes.height.nodeValue + 'px';
+        el.getContext().clearRect();
+        break;
+    }
+  }
+
+  function onResize(e) {
+    var el = e.srcElement;
+    if (el.firstChild) {
+      el.firstChild.style.width =  el.clientWidth + 'px';
+      el.firstChild.style.height = el.clientHeight + 'px';
+    }
+  }
+
+  G_vmlCanvasManager_.init();
+
+  // precompute "00" to "FF"
+  var dec2hex = [];
+  for (var i = 0; i < 16; i++) {
+    for (var j = 0; j < 16; j++) {
+      dec2hex[i * 16 + j] = i.toString(16) + j.toString(16);
+    }
+  }
+
+  function createMatrixIdentity() {
+    return [
+      [1, 0, 0],
+      [0, 1, 0],
+      [0, 0, 1]
+    ];
+  }
+
+  function matrixMultiply(m1, m2) {
+    var result = createMatrixIdentity();
+
+    for (var x = 0; x < 3; x++) {
+      for (var y = 0; y < 3; y++) {
+        var sum = 0;
+
+        for (var z = 0; z < 3; z++) {
+          sum += m1[x][z] * m2[z][y];
+        }
+
+        result[x][y] = sum;
+      }
+    }
+    return result;
+  }
+
+  function copyState(o1, o2) {
+    o2.fillStyle     = o1.fillStyle;
+    o2.lineCap       = o1.lineCap;
+    o2.lineJoin      = o1.lineJoin;
+    o2.lineWidth     = o1.lineWidth;
+    o2.miterLimit    = o1.miterLimit;
+    o2.shadowBlur    = o1.shadowBlur;
+    o2.shadowColor   = o1.shadowColor;
+    o2.shadowOffsetX = o1.shadowOffsetX;
+    o2.shadowOffsetY = o1.shadowOffsetY;
+    o2.strokeStyle   = o1.strokeStyle;
+    o2.globalAlpha   = o1.globalAlpha;
+    o2.arcScaleX_    = o1.arcScaleX_;
+    o2.arcScaleY_    = o1.arcScaleY_;
+    o2.lineScale_    = o1.lineScale_;
+  }
+
+  function processStyle(styleString) {
+    var str, alpha = 1;
+
+    styleString = String(styleString);
+    if (styleString.substring(0, 3) == 'rgb') {
+      var start = styleString.indexOf('(', 3);
+      var end = styleString.indexOf(')', start + 1);
+      var guts = styleString.substring(start + 1, end).split(',');
+
+      str = '#';
+      for (var i = 0; i < 3; i++) {
+        str += dec2hex[Number(guts[i])];
+      }
+
+      if (guts.length == 4 && styleString.substr(3, 1) == 'a') {
+        alpha = guts[3];
+      }
+    } else {
+      str = styleString;
+    }
+
+    return {color: str, alpha: alpha};
+  }
+
+  function processLineCap(lineCap) {
+    switch (lineCap) {
+      case 'butt':
+        return 'flat';
+      case 'round':
+        return 'round';
+      case 'square':
+      default:
+        return 'square';
+    }
+  }
+
+  /**
+   * This class implements CanvasRenderingContext2D interface as described by
+   * the WHATWG.
+   * @param {HTMLElement} surfaceElement The element that the 2D context should
+   * be associated with
+   */
+  function CanvasRenderingContext2D_(surfaceElement) {
+    this.m_ = createMatrixIdentity();
+
+    this.mStack_ = [];
+    this.aStack_ = [];
+    this.currentPath_ = [];
+
+    // Canvas context properties
+    this.strokeStyle = '#000';
+    this.fillStyle = '#000';
+
+    this.lineWidth = 1;
+    this.lineJoin = 'miter';
+    this.lineCap = 'butt';
+    this.miterLimit = Z * 1;
+    this.globalAlpha = 1;
+    this.canvas = surfaceElement;
+
+    var el = surfaceElement.ownerDocument.createElement('div');
+    el.style.width =  surfaceElement.clientWidth + 'px';
+    el.style.height = surfaceElement.clientHeight + 'px';
+    el.style.overflow = 'hidden';
+    el.style.position = 'absolute';
+    surfaceElement.appendChild(el);
+
+    this.element_ = el;
+    this.arcScaleX_ = 1;
+    this.arcScaleY_ = 1;
+    this.lineScale_ = 1;
+  }
+
+  var contextPrototype = CanvasRenderingContext2D_.prototype;
+  contextPrototype.clearRect = function() {
+    this.element_.innerHTML = '';
+  };
+
+  contextPrototype.beginPath = function() {
+    // TODO: Branch current matrix so that save/restore has no effect
+    //       as per safari docs.
+    this.currentPath_ = [];
+  };
+
+  contextPrototype.moveTo = function(aX, aY) {
+    var p = this.getCoords_(aX, aY);
+    this.currentPath_.push({type: 'moveTo', x: p.x, y: p.y});
+    this.currentX_ = p.x;
+    this.currentY_ = p.y;
+  };
+
+  contextPrototype.lineTo = function(aX, aY) {
+    var p = this.getCoords_(aX, aY);
+    this.currentPath_.push({type: 'lineTo', x: p.x, y: p.y});
+
+    this.currentX_ = p.x;
+    this.currentY_ = p.y;
+  };
+
+  contextPrototype.bezierCurveTo = function(aCP1x, aCP1y,
+                                            aCP2x, aCP2y,
+                                            aX, aY) {
+    var p = this.getCoords_(aX, aY);
+    var cp1 = this.getCoords_(aCP1x, aCP1y);
+    var cp2 = this.getCoords_(aCP2x, aCP2y);
+    bezierCurveTo(this, cp1, cp2, p);
+  };
+
+  // Helper function that takes the already fixed cordinates.
+  function bezierCurveTo(self, cp1, cp2, p) {
+    self.currentPath_.push({
+      type: 'bezierCurveTo',
+      cp1x: cp1.x,
+      cp1y: cp1.y,
+      cp2x: cp2.x,
+      cp2y: cp2.y,
+      x: p.x,
+      y: p.y
+    });
+    self.currentX_ = p.x;
+    self.currentY_ = p.y;
+  }
+
+  contextPrototype.quadraticCurveTo = function(aCPx, aCPy, aX, aY) {
+    // the following is lifted almost directly from
+    // http://developer.mozilla.org/en/docs/Canvas_tutorial:Drawing_shapes
+
+    var cp = this.getCoords_(aCPx, aCPy);
+    var p = this.getCoords_(aX, aY);
+
+    var cp1 = {
+      x: this.currentX_ + 2.0 / 3.0 * (cp.x - this.currentX_),
+      y: this.currentY_ + 2.0 / 3.0 * (cp.y - this.currentY_)
+    };
+    var cp2 = {
+      x: cp1.x + (p.x - this.currentX_) / 3.0,
+      y: cp1.y + (p.y - this.currentY_) / 3.0
+    };
+
+    bezierCurveTo(this, cp1, cp2, p);
+  };
+
+  contextPrototype.arc = function(aX, aY, aRadius,
+                                  aStartAngle, aEndAngle, aClockwise) {
+    aRadius *= Z;
+    var arcType = aClockwise ? 'at' : 'wa';
+
+    var xStart = aX + mc(aStartAngle) * aRadius - Z2;
+    var yStart = aY + ms(aStartAngle) * aRadius - Z2;
+
+    var xEnd = aX + mc(aEndAngle) * aRadius - Z2;
+    var yEnd = aY + ms(aEndAngle) * aRadius - Z2;
+
+    // IE won't render arches drawn counter clockwise if xStart == xEnd.
+    if (xStart == xEnd && !aClockwise) {
+      xStart += 0.125; // Offset xStart by 1/80 of a pixel. Use something
+                       // that can be represented in binary
+    }
+
+    var p = this.getCoords_(aX, aY);
+    var pStart = this.getCoords_(xStart, yStart);
+    var pEnd = this.getCoords_(xEnd, yEnd);
+
+    this.currentPath_.push({type: arcType,
+                           x: p.x,
+                           y: p.y,
+                           radius: aRadius,
+                           xStart: pStart.x,
+                           yStart: pStart.y,
+                           xEnd: pEnd.x,
+                           yEnd: pEnd.y});
+
+  };
+
+  contextPrototype.rect = function(aX, aY, aWidth, aHeight) {
+    this.moveTo(aX, aY);
+    this.lineTo(aX + aWidth, aY);
+    this.lineTo(aX + aWidth, aY + aHeight);
+    this.lineTo(aX, aY + aHeight);
+    this.closePath();
+  };
+
+  contextPrototype.strokeRect = function(aX, aY, aWidth, aHeight) {
+    var oldPath = this.currentPath_;
+    this.beginPath();
+
+    this.moveTo(aX, aY);
+    this.lineTo(aX + aWidth, aY);
+    this.lineTo(aX + aWidth, aY + aHeight);
+    this.lineTo(aX, aY + aHeight);
+    this.closePath();
+    this.stroke();
+
+    this.currentPath_ = oldPath;
+  };
+
+  contextPrototype.fillRect = function(aX, aY, aWidth, aHeight) {
+    var oldPath = this.currentPath_;
+    this.beginPath();
+
+    this.moveTo(aX, aY);
+    this.lineTo(aX + aWidth, aY);
+    this.lineTo(aX + aWidth, aY + aHeight);
+    this.lineTo(aX, aY + aHeight);
+    this.closePath();
+    this.fill();
+
+    this.currentPath_ = oldPath;
+  };
+
+  contextPrototype.createLinearGradient = function(aX0, aY0, aX1, aY1) {
+    var gradient = new CanvasGradient_('gradient');
+    gradient.x0_ = aX0;
+    gradient.y0_ = aY0;
+    gradient.x1_ = aX1;
+    gradient.y1_ = aY1;
+    return gradient;
+  };
+
+  contextPrototype.createRadialGradient = function(aX0, aY0, aR0,
+                                                   aX1, aY1, aR1) {
+    var gradient = new CanvasGradient_('gradientradial');
+    gradient.x0_ = aX0;
+    gradient.y0_ = aY0;
+    gradient.r0_ = aR0;
+    gradient.x1_ = aX1;
+    gradient.y1_ = aY1;
+    gradient.r1_ = aR1;
+    return gradient;
+  };
+
+  contextPrototype.drawImage = function(image, var_args) {
+    var dx, dy, dw, dh, sx, sy, sw, sh;
+
+    // to find the original width we overide the width and height
+    var oldRuntimeWidth = image.runtimeStyle.width;
+    var oldRuntimeHeight = image.runtimeStyle.height;
+    image.runtimeStyle.width = 'auto';
+    image.runtimeStyle.height = 'auto';
+
+    // get the original size
+    var w = image.width;
+    var h = image.height;
+
+    // and remove overides
+    image.runtimeStyle.width = oldRuntimeWidth;
+    image.runtimeStyle.height = oldRuntimeHeight;
+
+    if (arguments.length == 3) {
+      dx = arguments[1];
+      dy = arguments[2];
+      sx = sy = 0;
+      sw = dw = w;
+      sh = dh = h;
+    } else if (arguments.length == 5) {
+      dx = arguments[1];
+      dy = arguments[2];
+      dw = arguments[3];
+      dh = arguments[4];
+      sx = sy = 0;
+      sw = w;
+      sh = h;
+    } else if (arguments.length == 9) {
+      sx = arguments[1];
+      sy = arguments[2];
+      sw = arguments[3];
+      sh = arguments[4];
+      dx = arguments[5];
+      dy = arguments[6];
+      dw = arguments[7];
+      dh = arguments[8];
+    } else {
+      throw Error('Invalid number of arguments');
+    }
+
+    var d = this.getCoords_(dx, dy);
+
+    var w2 = sw / 2;
+    var h2 = sh / 2;
+
+    var vmlStr = [];
+
+    var W = 10;
+    var H = 10;
+
+    // For some reason that I've now forgotten, using divs didn't work
+    vmlStr.push(' <g_vml_:group',
+                ' coordsize="', Z * W, ',', Z * H, '"',
+                ' coordorigin="0,0"' ,
+                ' style="width:', W, 'px;height:', H, 'px;position:absolute;');
+
+    // If filters are necessary (rotation exists), create them
+    // filters are bog-slow, so only create them if abbsolutely necessary
+    // The following check doesn't account for skews (which don't exist
+    // in the canvas spec (yet) anyway.
+
+    if (this.m_[0][0] != 1 || this.m_[0][1]) {
+      var filter = [];
+
+      // Note the 12/21 reversal
+      filter.push('M11=', this.m_[0][0], ',',
+                  'M12=', this.m_[1][0], ',',
+                  'M21=', this.m_[0][1], ',',
+                  'M22=', this.m_[1][1], ',',
+                  'Dx=', mr(d.x / Z), ',',
+                  'Dy=', mr(d.y / Z), '');
+
+      // Bounding box calculation (need to minimize displayed area so that
+      // filters don't waste time on unused pixels.
+      var max = d;
+      var c2 = this.getCoords_(dx + dw, dy);
+      var c3 = this.getCoords_(dx, dy + dh);
+      var c4 = this.getCoords_(dx + dw, dy + dh);
+
+      max.x = m.max(max.x, c2.x, c3.x, c4.x);
+      max.y = m.max(max.y, c2.y, c3.y, c4.y);
+
+      vmlStr.push('padding:0 ', mr(max.x / Z), 'px ', mr(max.y / Z),
+                  'px 0;filter:progid:DXImageTransform.Microsoft.Matrix(',
+                  filter.join(''), ", sizingmethod='clip');")
+    } else {
+      vmlStr.push('top:', mr(d.y / Z), 'px;left:', mr(d.x / Z), 'px;');
+    }
+
+    vmlStr.push(' ">' ,
+                '<g_vml_:image src="', image.src, '"',
+                ' style="width:', Z * dw, 'px;',
+                ' height:', Z * dh, 'px;"',
+                ' cropleft="', sx / w, '"',
+                ' croptop="', sy / h, '"',
+                ' cropright="', (w - sx - sw) / w, '"',
+                ' cropbottom="', (h - sy - sh) / h, '"',
+                ' />',
+                '</g_vml_:group>');
+
+    this.element_.insertAdjacentHTML('BeforeEnd',
+                                    vmlStr.join(''));
+  };
+
+  contextPrototype.stroke = function(aFill) {
+    var lineStr = [];
+    var lineOpen = false;
+    var a = processStyle(aFill ? this.fillStyle : this.strokeStyle);
+    var color = a.color;
+    var opacity = a.alpha * this.globalAlpha;
+
+    var W = 10;
+    var H = 10;
+
+    lineStr.push('<g_vml_:shape',
+                 ' filled="', !!aFill, '"',
+                 ' style="position:absolute;width:', W, 'px;height:', H, 'px;"',
+                 ' coordorigin="0 0" coordsize="', Z * W, ' ', Z * H, '"',
+                 ' stroked="', !aFill, '"',
+                 ' path="');
+
+    var newSeq = false;
+    var min = {x: null, y: null};
+    var max = {x: null, y: null};
+
+    for (var i = 0; i < this.currentPath_.length; i++) {
+      var p = this.currentPath_[i];
+      var c;
+
+      switch (p.type) {
+        case 'moveTo':
+          c = p;
+          lineStr.push(' m ', mr(p.x), ',', mr(p.y));
+          break;
+        case 'lineTo':
+          lineStr.push(' l ', mr(p.x), ',', mr(p.y));
+          break;
+        case 'close':
+          lineStr.push(' x ');
+          p = null;
+          break;
+        case 'bezierCurveTo':
+          lineStr.push(' c ',
+                       mr(p.cp1x), ',', mr(p.cp1y), ',',
+                       mr(p.cp2x), ',', mr(p.cp2y), ',',
+                       mr(p.x), ',', mr(p.y));
+          break;
+        case 'at':
+        case 'wa':
+          lineStr.push(' ', p.type, ' ',
+                       mr(p.x - this.arcScaleX_ * p.radius), ',',
+                       mr(p.y - this.arcScaleY_ * p.radius), ' ',
+                       mr(p.x + this.arcScaleX_ * p.radius), ',',
+                       mr(p.y + this.arcScaleY_ * p.radius), ' ',
+                       mr(p.xStart), ',', mr(p.yStart), ' ',
+                       mr(p.xEnd), ',', mr(p.yEnd));
+          break;
+      }
+
+
+      // TODO: Following is broken for curves due to
+      //       move to proper paths.
+
+      // Figure out dimensions so we can do gradient fills
+      // properly
+      if (p) {
+        if (min.x == null || p.x < min.x) {
+          min.x = p.x;
+        }
+        if (max.x == null || p.x > max.x) {
+          max.x = p.x;
+        }
+        if (min.y == null || p.y < min.y) {
+          min.y = p.y;
+        }
+        if (max.y == null || p.y > max.y) {
+          max.y = p.y;
+        }
+      }
+    }
+    lineStr.push(' ">');
+
+    if (!aFill) {
+      var lineWidth = this.lineScale_ * this.lineWidth;
+
+      // VML cannot correctly render a line if the width is less than 1px.
+      // In that case, we dilute the color to make the line look thinner.
+      if (lineWidth < 1) {
+        opacity *= lineWidth;
+      }
+
+      lineStr.push(
+        '<g_vml_:stroke',
+        ' opacity="', opacity, '"',
+        ' joinstyle="', this.lineJoin, '"',
+        ' miterlimit="', this.miterLimit, '"',
+        ' endcap="', processLineCap(this.lineCap), '"',
+        ' weight="', lineWidth, 'px"',
+        ' color="', color, '" />'
+      );
+    } else if (typeof this.fillStyle == 'object') {
+      var fillStyle = this.fillStyle;
+      var angle = 0;
+      var focus = {x: 0, y: 0};
+
+      // additional offset
+      var shift = 0;
+      // scale factor for offset
+      var expansion = 1;
+
+      if (fillStyle.type_ == 'gradient') {
+        var x0 = fillStyle.x0_ / this.arcScaleX_;
+        var y0 = fillStyle.y0_ / this.arcScaleY_;
+        var x1 = fillStyle.x1_ / this.arcScaleX_;
+        var y1 = fillStyle.y1_ / this.arcScaleY_;
+        var p0 = this.getCoords_(x0, y0);
+        var p1 = this.getCoords_(x1, y1);
+        var dx = p1.x - p0.x;
+        var dy = p1.y - p0.y;
+        angle = Math.atan2(dx, dy) * 180 / Math.PI;
+
+        // The angle should be a non-negative number.
+        if (angle < 0) {
+          angle += 360;
+        }
+
+        // Very small angles produce an unexpected result because they are
+        // converted to a scientific notation string.
+        if (angle < 1e-6) {
+          angle = 0;
+        }
+      } else {
+        var p0 = this.getCoords_(fillStyle.x0_, fillStyle.y0_);
+        var width  = max.x - min.x;
+        var height = max.y - min.y;
+        focus = {
+          x: (p0.x - min.x) / width,
+          y: (p0.y - min.y) / height
+        };
+
+        width  /= this.arcScaleX_ * Z;
+        height /= this.arcScaleY_ * Z;
+        var dimension = m.max(width, height);
+        shift = 2 * fillStyle.r0_ / dimension;
+        expansion = 2 * fillStyle.r1_ / dimension - shift;
+      }
+
+      // We need to sort the color stops in ascending order by offset,
+      // otherwise IE won't interpret it correctly.
+      var stops = fillStyle.colors_;
+      stops.sort(function(cs1, cs2) {
+        return cs1.offset - cs2.offset;
+      });
+
+      var length = stops.length;
+      var color1 = stops[0].color;
+      var color2 = stops[length - 1].color;
+      var opacity1 = stops[0].alpha * this.globalAlpha;
+      var opacity2 = stops[length - 1].alpha * this.globalAlpha;
+
+      var colors = [];
+      for (var i = 0; i < length; i++) {
+        var stop = stops[i];
+        colors.push(stop.offset * expansion + shift + ' ' + stop.color);
+      }
+
+      // When colors attribute is used, the meanings of opacity and o:opacity2
+      // are reversed.
+      lineStr.push('<g_vml_:fill type="', fillStyle.type_, '"',
+                   ' method="none" focus="100%"',
+                   ' color="', color1, '"',
+                   ' color2="', color2, '"',
+                   ' colors="', colors.join(','), '"',
+                   ' opacity="', opacity2, '"',
+                   ' g_o_:opacity2="', opacity1, '"',
+                   ' angle="', angle, '"',
+                   ' focusposition="', focus.x, ',', focus.y, '" />');
+    } else {
+      lineStr.push('<g_vml_:fill color="', color, '" opacity="', opacity,
+                   '" />');
+    }
+
+    lineStr.push('</g_vml_:shape>');
+
+    this.element_.insertAdjacentHTML('beforeEnd', lineStr.join(''));
+  };
+
+  contextPrototype.fill = function() {
+    this.stroke(true);
+  }
+
+  contextPrototype.closePath = function() {
+    this.currentPath_.push({type: 'close'});
+  };
+
+  /**
+   * @private
+   */
+  contextPrototype.getCoords_ = function(aX, aY) {
+    var m = this.m_;
+    return {
+      x: Z * (aX * m[0][0] + aY * m[1][0] + m[2][0]) - Z2,
+      y: Z * (aX * m[0][1] + aY * m[1][1] + m[2][1]) - Z2
+    }
+  };
+
+  contextPrototype.save = function() {
+    var o = {};
+    copyState(this, o);
+    this.aStack_.push(o);
+    this.mStack_.push(this.m_);
+    this.m_ = matrixMultiply(createMatrixIdentity(), this.m_);
+  };
+
+  contextPrototype.restore = function() {
+    copyState(this.aStack_.pop(), this);
+    this.m_ = this.mStack_.pop();
+  };
+
+  contextPrototype.translate = function(aX, aY) {
+    var m1 = [
+      [1,  0,  0],
+      [0,  1,  0],
+      [aX, aY, 1]
+    ];
+
+    this.m_ = matrixMultiply(m1, this.m_);
+  };
+
+  contextPrototype.rotate = function(aRot) {
+    var c = mc(aRot);
+    var s = ms(aRot);
+
+    var m1 = [
+      [c,  s, 0],
+      [-s, c, 0],
+      [0,  0, 1]
+    ];
+
+    this.m_ = matrixMultiply(m1, this.m_);
+  };
+
+  contextPrototype.scale = function(aX, aY) {
+    this.arcScaleX_ *= aX;
+    this.arcScaleY_ *= aY;
+    var m1 = [
+      [aX, 0,  0],
+      [0,  aY, 0],
+      [0,  0,  1]
+    ];
+
+    var m = this.m_ = matrixMultiply(m1, this.m_);
+
+    // Get the line scale.
+    // Determinant of this.m_ means how much the area is enlarged by the
+    // transformation. So its square root can be used as a scale factor
+    // for width.
+    var det = m[0][0] * m[1][1] - m[0][1] * m[1][0];
+    this.lineScale_ = sqrt(abs(det));
+  };
+
+  /******** STUBS ********/
+  contextPrototype.clip = function() {
+    // TODO: Implement
+  };
+
+  contextPrototype.arcTo = function() {
+    // TODO: Implement
+  };
+
+  contextPrototype.createPattern = function() {
+    return new CanvasPattern_;
+  };
+
+  // Gradient / Pattern Stubs
+  function CanvasGradient_(aType) {
+    this.type_ = aType;
+    this.x0_ = 0;
+    this.y0_ = 0;
+    this.r0_ = 0;
+    this.x1_ = 0;
+    this.y1_ = 0;
+    this.r1_ = 0;
+    this.colors_ = [];
+  }
+
+  CanvasGradient_.prototype.addColorStop = function(aOffset, aColor) {
+    aColor = processStyle(aColor);
+    this.colors_.push({offset: aOffset,
+                       color: aColor.color,
+                       alpha: aColor.alpha});
+  };
+
+  function CanvasPattern_() {}
+
+  // set up externs
+  G_vmlCanvasManager = G_vmlCanvasManager_;
+  CanvasRenderingContext2D = CanvasRenderingContext2D_;
+  CanvasGradient = CanvasGradient_;
+  CanvasPattern = CanvasPattern_;
+
+})();
+
+} // if

docs/excanvas.min.js

+if(!document.createElement("canvas").getContext){(function(){var R=Math;var S=R.round;var O=R.sin;var a=R.cos;var J=R.abs;var Y=R.sqrt;var A=10;var K=A/2;function G(){return this.context_||(this.context_=new M(this))}var Q=Array.prototype.slice;function b(c,d,e){var Z=Q.call(arguments,2);return function(){return c.apply(d,Z.concat(Q.call(arguments)))}}var H={init:function(Z){if(/MSIE/.test(navigator.userAgent)&&!window.opera){var c=Z||document;c.createElement("canvas");c.attachEvent("onreadystatechange",b(this.init_,this,c))}},init_:function(e){if(!e.namespaces.g_vml_){e.namespaces.add("g_vml_","urn:schemas-microsoft-com:vml","#default#VML")}if(!e.namespaces.g_o_){e.namespaces.add("g_o_","urn:schemas-microsoft-com:office:office","#default#VML")}if(!e.styleSheets.ex_canvas_){var d=e.createStyleSheet();d.owningElement.id="ex_canvas_";d.cssText="canvas{display:inline-block;overflow:hidden;text-align:left;width:300px;height:150px}g_vml_\\:*{behavior:url(#default#VML)}g_o_\\:*{behavior:url(#default#VML)}"}var c=e.getElementsByTagName("canvas");for(var Z=0;Z<c.length;Z++){this.initElement(c[Z])}},initElement:function(c){if(!c.getContext){c.getContext=G;c.innerHTML="";c.attachEvent("onpropertychange",X);c.attachEvent("onresize",B);var Z=c.attributes;if(Z.width&&Z.width.specified){c.style.width=Z.width.nodeValue+"px"}else{c.width=c.clientWidth}if(Z.height&&Z.height.specified){c.style.height=Z.height.nodeValue+"px"}else{c.height=c.clientHeight}}return c}};function X(c){var Z=c.srcElement;switch(c.propertyName){case"width":Z.style.width=Z.attributes.width.nodeValue+"px";Z.getContext().clearRect();break;case"height":Z.style.height=Z.attributes.height.nodeValue+"px";Z.getContext().clearRect();break}}function B(c){var Z=c.srcElement;if(Z.firstChild){Z.firstChild.style.width=Z.clientWidth+"px";Z.firstChild.style.height=Z.clientHeight+"px"}}H.init();var E=[];for(var V=0;V<16;V++){for(var U=0;U<16;U++){E[V*16+U]=V.toString(16)+U.toString(16)}}function N(){return[[1,0,0],[0,1,0],[0,0,1]]}function D(e,d){var c=N();for(var Z=0;Z<3;Z++){for(var h=0;h<3;h++){var f=0;for(var g=0;g<3;g++){f+=e[Z][g]*d[g][h]}c[Z][h]=f}}return c}function T(c,Z){Z.fillStyle=c.fillStyle;Z.lineCap=c.lineCap;Z.lineJoin=c.lineJoin;Z.lineWidth=c.lineWidth;Z.miterLimit=c.miterLimit;Z.shadowBlur=c.shadowBlur;Z.shadowColor=c.shadowColor;Z.shadowOffsetX=c.shadowOffsetX;Z.shadowOffsetY=c.shadowOffsetY;Z.strokeStyle=c.strokeStyle;Z.globalAlpha=c.globalAlpha;Z.arcScaleX_=c.arcScaleX_;Z.arcScaleY_=c.arcScaleY_;Z.lineScale_=c.lineScale_}function C(c){var f,e=1;c=String(c);if(c.substring(0,3)=="rgb"){var h=c.indexOf("(",3);var Z=c.indexOf(")",h+1);var g=c.substring(h+1,Z).split(",");f="#";for(var d=0;d<3;d++){f+=E[Number(g[d])]}if(g.length==4&&c.substr(3,1)=="a"){e=g[3]}}else{f=c}return{color:f,alpha:e}}function P(Z){switch(Z){case"butt":return"flat";case"round":return"round";case"square":default:return"square"}}function M(c){this.m_=N();this.mStack_=[];this.aStack_=[];this.currentPath_=[];this.strokeStyle="#000";this.fillStyle="#000";this.lineWidth=1;this.lineJoin="miter";this.lineCap="butt";this.miterLimit=A*1;this.globalAlpha=1;this.canvas=c;var Z=c.ownerDocument.createElement("div");Z.style.width=c.clientWidth+"px";Z.style.height=c.clientHeight+"px";Z.style.overflow="hidden";Z.style.position="absolute";c.appendChild(Z);this.element_=Z;this.arcScaleX_=1;this.arcScaleY_=1;this.lineScale_=1}var I=M.prototype;I.clearRect=function(){this.element_.innerHTML=""};I.beginPath=function(){this.currentPath_=[]};I.moveTo=function(c,Z){var d=this.getCoords_(c,Z);this.currentPath_.push({type:"moveTo",x:d.x,y:d.y});this.currentX_=d.x;this.currentY_=d.y};I.lineTo=function(c,Z){var d=this.getCoords_(c,Z);this.currentPath_.push({type:"lineTo",x:d.x,y:d.y});this.currentX_=d.x;this.currentY_=d.y};I.bezierCurveTo=function(d,c,j,i,h,f){var Z=this.getCoords_(h,f);var g=this.getCoords_(d,c);var e=this.getCoords_(j,i);L(this,g,e,Z)};function L(Z,e,d,c){Z.currentPath_.push({type:"bezierCurveTo",cp1x:e.x,cp1y:e.y,cp2x:d.x,cp2y:d.y,x:c.x,y:c.y});Z.currentX_=c.x;Z.currentY_=c.y}I.quadraticCurveTo=function(h,d,c,Z){var g=this.getCoords_(h,d);var f=this.getCoords_(c,Z);var i={x:this.currentX_+2/3*(g.x-this.currentX_),y:this.currentY_+2/3*(g.y-this.currentY_)};var e={x:i.x+(f.x-this.currentX_)/3,y:i.y+(f.y-this.currentY_)/3};L(this,i,e,f)};I.arc=function(k,i,j,f,c,d){j*=A;var o=d?"at":"wa";var l=k+a(f)*j-K;var n=i+O(f)*j-K;var Z=k+a(c)*j-K;var m=i+O(c)*j-K;if(l==Z&&!d){l+=0.125}var e=this.getCoords_(k,i);var h=this.getCoords_(l,n);var g=this.getCoords_(Z,m);this.currentPath_.push({type:o,x:e.x,y:e.y,radius:j,xStart:h.x,yStart:h.y,xEnd:g.x,yEnd:g.y})};I.rect=function(d,c,Z,e){this.moveTo(d,c);this.lineTo(d+Z,c);this.lineTo(d+Z,c+e);this.lineTo(d,c+e);this.closePath()};I.strokeRect=function(d,c,Z,e){var f=this.currentPath_;this.beginPath();this.moveTo(d,c);this.lineTo(d+Z,c);this.lineTo(d+Z,c+e);this.lineTo(d,c+e);this.closePath();this.stroke();this.currentPath_=f};I.fillRect=function(d,c,Z,e){var f=this.currentPath_;this.beginPath();this.moveTo(d,c);this.lineTo(d+Z,c);this.lineTo(d+Z,c+e);this.lineTo(d,c+e);this.closePath();this.fill();this.currentPath_=f};I.createLinearGradient=function(c,e,Z,d){var f=new W("gradient");f.x0_=c;f.y0_=e;f.x1_=Z;f.y1_=d;return f};I.createRadialGradient=function(e,g,d,c,f,Z){var h=new W("gradientradial");h.x0_=e;h.y0_=g;h.r0_=d;h.x1_=c;h.y1_=f;h.r1_=Z;return h};I.drawImage=function(s,e){var l,j,n,AA,q,o,u,AC;var m=s.runtimeStyle.width;var r=s.runtimeStyle.height;s.runtimeStyle.width="auto";s.runtimeStyle.height="auto";var k=s.width;var y=s.height;s.runtimeStyle.width=m;s.runtimeStyle.height=r;if(arguments.length==3){l=arguments[1];j=arguments[2];q=o=0;u=n=k;AC=AA=y}else{if(arguments.length==5){l=arguments[1];j=arguments[2];n=arguments[3];AA=arguments[4];q=o=0;u=k;AC=y}else{if(arguments.length==9){q=arguments[1];o=arguments[2];u=arguments[3];AC=arguments[4];l=arguments[5];j=arguments[6];n=arguments[7];AA=arguments[8]}else{throw Error("Invalid number of arguments")}}}var AB=this.getCoords_(l,j);var f=u/2;var c=AC/2;var z=[];var Z=10;var i=10;z.push(" <g_vml_:group",' coordsize="',A*Z,",",A*i,'"',' coordorigin="0,0"',' style="width:',Z,"px;height:",i,"px;position:absolute;");if(this.m_[0][0]!=1||this.m_[0][1]){var g=[];g.push("M11=",this.m_[0][0],",","M12=",this.m_[1][0],",","M21=",this.m_[0][1],",","M22=",this.m_[1][1],",","Dx=",S(AB.x/A),",","Dy=",S(AB.y/A),"");var x=AB;var v=this.getCoords_(l+n,j);var t=this.getCoords_(l,j+AA);var p=this.getCoords_(l+n,j+AA);x.x=R.max(x.x,v.x,t.x,p.x);x.y=R.max(x.y,v.y,t.y,p.y);z.push("padding:0 ",S(x.x/A),"px ",S(x.y/A),"px 0;filter:progid:DXImageTransform.Microsoft.Matrix(",g.join(""),", sizingmethod='clip');")}else{z.push("top:",S(AB.y/A),"px;left:",S(AB.x/A),"px;")}z.push(' ">','<g_vml_:image src="',s.src,'"',' style="width:',A*n,"px;"," height:",A*AA,'px;"',' cropleft="',q/k,'"',' croptop="',o/y,'"',' cropright="',(k-q-u)/k,'"',' cropbottom="',(y-o-AC)/y,'"'," />","</g_vml_:group>");this.element_.insertAdjacentHTML("BeforeEnd",z.join(""))};I.stroke=function(AE){var j=[];var k=false;var AP=C(AE?this.fillStyle:this.strokeStyle);var AA=AP.color;var AK=AP.alpha*this.globalAlpha;var f=10;var m=10;j.push("<g_vml_:shape",' filled="',!!AE,'"',' style="position:absolute;width:',f,"px;height:",m,'px;"',' coordorigin="0 0" coordsize="',A*f," ",A*m,'"',' stroked="',!AE,'"',' path="');var l=false;var AO={x:null,y:null};var w={x:null,y:null};for(var AJ=0;AJ<this.currentPath_.length;AJ++){var AI=this.currentPath_[AJ];var AN;switch(AI.type){case"moveTo":AN=AI;j.push(" m ",S(AI.x),",",S(AI.y));break;case"lineTo":j.push(" l ",S(AI.x),",",S(AI.y));break;case"close":j.push(" x ");AI=null;break;case"bezierCurveTo":j.push(" c ",S(AI.cp1x),",",S(AI.cp1y),",",S(AI.cp2x),",",S(AI.cp2y),",",S(AI.x),",",S(AI.y));break;case"at":case"wa":j.push(" ",AI.type," ",S(AI.x-this.arcScaleX_*AI.radius),",",S(AI.y-this.arcScaleY_*AI.radius)," ",S(AI.x+this.arcScaleX_*AI.radius),",",S(AI.y+this.arcScaleY_*AI.radius)," ",S(AI.xStart),",",S(AI.yStart)," ",S(AI.xEnd),",",S(AI.yEnd));break}if(AI){if(AO.x==null||AI.x<AO.x){AO.x=AI.x}if(w.x==null||AI.x>w.x){w.x=AI.x}if(AO.y==null||AI.y<AO.y){AO.y=AI.y}if(w.y==null||AI.y>w.y){w.y=AI.y}}}j.push(' ">');if(!AE){var v=this.lineScale_*this.lineWidth;if(v<1){AK*=v}j.push("<g_vml_:stroke",' opacity="',AK,'"',' joinstyle="',this.lineJoin,'"',' miterlimit="',this.miterLimit,'"',' endcap="',P(this.lineCap),'"',' weight="',v,'px"',' color="',AA,'" />')}else{if(typeof this.fillStyle=="object"){var n=this.fillStyle;var t=0;var AH={x:0,y:0};var AB=0;var r=1;if(n.type_=="gradient"){var q=n.x0_/this.arcScaleX_;var d=n.y0_/this.arcScaleY_;var o=n.x1_/this.arcScaleX_;var AQ=n.y1_/this.arcScaleY_;var AM=this.getCoords_(q,d);var AL=this.getCoords_(o,AQ);var h=AL.x-AM.x;var g=AL.y-AM.y;t=Math.atan2(h,g)*180/Math.PI;if(t<0){t+=360}if(t<0.000001){t=0}}else{var AM=this.getCoords_(n.x0_,n.y0_);var Z=w.x-AO.x;var e=w.y-AO.y;AH={x:(AM.x-AO.x)/Z,y:(AM.y-AO.y)/e};Z/=this.arcScaleX_*A;e/=this.arcScaleY_*A;var AG=R.max(Z,e);AB=2*n.r0_/AG;r=2*n.r1_/AG-AB}var z=n.colors_;z.sort(function(i,c){return i.offset-c.offset});var u=z.length;var y=z[0].color;var x=z[u-1].color;var AD=z[0].alpha*this.globalAlpha;var AC=z[u-1].alpha*this.globalAlpha;var AF=[];for(var AJ=0;AJ<u;AJ++){var s=z[AJ];AF.push(s.offset*r+AB+" "+s.color)}j.push('<g_vml_:fill type="',n.type_,'"',' method="none" focus="100%"',' color="',y,'"',' color2="',x,'"',' colors="',AF.join(","),'"',' opacity="',AC,'"',' g_o_:opacity2="',AD,'"',' angle="',t,'"',' focusposition="',AH.x,",",AH.y,'" />')}else{j.push('<g_vml_:fill color="',AA,'" opacity="',AK,'" />')}}j.push("</g_vml_:shape>");this.element_.insertAdjacentHTML("beforeEnd",j.join(""))};I.fill=function(){this.stroke(true)};I.closePath=function(){this.currentPath_.push({type:"close"})};I.getCoords_=function(d,c){var Z=this.m_;return{x:A*(d*Z[0][0]+c*Z[1][0]+Z[2][0])-K,y:A*(d*Z[0][1]+c*Z[1][1]+Z[2][1])-K}};I.save=function(){var Z={};T(this,Z);this.aStack_.push(Z);this.mStack_.push(this.m_);this.m_=D(N(),this.m_)};I.restore=function(){T(this.aStack_.pop(),this);this.m_=this.mStack_.pop()};I.translate=function(d,c){var Z=[[1,0,0],[0,1,0],[d,c,1]];this.m_=D(Z,this.m_)};I.rotate=function(d){var f=a(d);var e=O(d);var Z=[[f,e,0],[-e,f,0],[0,0,1]];this.m_=D(Z,this.m_)};I.scale=function(f,e){this.arcScaleX_*=f;this.arcScaleY_*=e;var c=[[f,0,0],[0,e,0],[0,0,1]];var Z=this.m_=D(c,this.m_);var d=Z[0][0]*Z[1][1]-Z[0][1]*Z[1][0];this.lineScale_=Y(J(d))};I.clip=function(){};I.arcTo=function(){};I.createPattern=function(){return new F};function W(Z){this.type_=Z;this.x0_=0;this.y0_=0;this.r0_=0;this.x1_=0;this.y1_=0;this.r1_=0;this.colors_=[]}W.prototype.addColorStop=function(c,Z){Z=C(Z);this.colors_.push({offset:c,color:Z.color,alpha:Z.alpha})};function F(){}G_vmlCanvasManager=H;CanvasRenderingContext2D=M;CanvasGradient=W;CanvasPattern=F})()};
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+ <head>
+    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+	<!--[if IE]><script language="javascript" type="text/javascript" src="excanvas.js"></script><![endif]-->
+    <script language="javascript" type="text/javascript" src="../jquery-1.3.2.js"></script>
+    <script language="javascript" type="text/javascript" src="jquery.jqplot.js"></script>
+    <script language="javascript" type="text/javascript" src="jqplot.trendLines.js"></script>
+    <script language="javascript">
+		$(document).ready(function(){
+			// chart = $.jqplot('chartdiv', [[[1,1],[2,4], [3,5], [4,5], [6,12]], [[32,18],[46,32],[79,47]]], {series:[{}, {xaxis:'x2axis', yaxis:'y2axis'}], axes:{y2axis:{scale:1.5}}});
+			// chart = $.jqplot('chartdiv', [[[1,1],[2,4], [3,5], [4,5], [6,12]], [[32,18],[46,14],[79,55]]]);
+			// chart = $.jqplot('chartdiv', [[[1,1],[2,4], [3,5], [4,1], null, [6,12]], [[32,18],[46,14],[79,55]]], {series:[{}, {xaxis:'x2axis', yaxis:'y2axis'}], axesDefaults:{ticks:{fontSize:'0.85em'}}, axes:{yaxis:{min:0, max:15}}, seriesDefaults:{lineWidth:2.5, shadowOffset:1, shadowDepth:3, shadowAlpha:0.07}});
+/*
+        chart1 = $.jqplot('chartdiv1', [[[1,1],[2,4], [4,5], [5,3], null, [7,8]], [[6,10],[10,7.7],[14,10],[18,15]]]);
+        chart2 = $.jqplot('chartdiv2', [[[1,1],[2,4], [4,5], [5,3], null, [7,8]], [[2,35],[5,30],[9,18],[11,21]]], {title:'Sample #2', series:[{}, {yaxis:'y2axis'}]});
+*/
+        chart = $.jqplot('chartdiv', 
+            [[[1,1],[2,4],[3,5],[4,1],[5,4],[6,10]], [[1,11],[2,18],[3,4],[4,50],[5,-5],[6,41]]], 
+            {
+                title:{text:'Dual Axis With Title and Trend Line'}, 
+                legend:{textColor:'', fontSize:'0.8em', location:'nw', rowSpacing:'0.2em'}, 
+                grid:{borderShadow:true, drawGridlines:true}, 
+                axesDefaults:{ticks:{mark:'cross', showLabels:true, size:6, fontSize:'0.75em'}}, 
+                series:[{trendLines:{show:true, label:'Line 1 Trend'}, label:'Line 1'}, {label:'Line 2', xaxis:'x2axis', yaxis:'y2axis'}],
+                axes:{y2axis:{min:0, max:42}}
+            });
+		});
+		</script>
+		<style type="text/css">
+		.jqplot {
+
+		}
+		</style>
+ </head>
+    <body>
+    <p></p>
+	<div id="chartdiv" class="jqplot" style="height:360px;width:540px; "></div>
+
+</body></html>

docs/jqplot.logAxisRenderer.js

+(function($) {
+    $.jqplot.logAxisRenderer = function() {
+    };
+    
+    $.jqplot.logAxisRenderer.prototype.fill = $.jqplot.linearAxisRenderer.fill;
+    
+
+    $.jqplot.logAxisRenderer.prototype.pack = function(offsets) {
+        var ticks = this.ticks;
+        var tickdivs = $(this.elem).children('div');
+        if (this.name == 'xaxis' || this.name == 'x2axis') {
+            this.offsets = {min:offsets.left, max:offsets.right};
+            
+            this.p2u = function(p) {
+                return (p - this.offsets.min)*(this.max - this.min)/(this.gridWidth - this.offsets.max - this.offsets.min) + this.min;
+            }
+            
+            this.u2p = function(u) {
+                return (u - this.min) * (this.gridWidth - this.offsets.max - this.offsets.min) / (this.max - this.min) + this.offsets.min;
+            }
+            
+            if (this.show) {
+                for (i=0; i<tickdivs.length; i++) {
+                    var shim = $(tickdivs[i]).outerWidth()/2;
+                    var t = this.u2p(ticks.values[i]);
+                    var val = this.u2p(ticks.values[i]) - shim + 'px';
+                    $(tickdivs[i]).css('left', val);
+                    // remember, could have done it this way
+                    //tickdivs[i].style.left = val;
+                }
+            }
+        }  
+        else {
+            this.offsets = {min:offsets.bottom, max:offsets.top};
+            
+            this.p2u = function(p) {
+                return (p - this.gridHeight + this.offsets.min)*(this.max - this.min)/(this.gridHeight - this.offsets.min - this.offsets.max) + this.min;
+            }
+            
+            this.u2p = function(u) {
+                return -(u - this.min) * (this.gridHeight - this.offsets.min - this.offsets.max) / (this.max - this.min) + this.gridHeight - this.offsets.min;
+            }
+            if (this.show) {
+                for (i=0; i<tickdivs.length; i++) {
+                    var shim = $(tickdivs[i]).outerHeight()/2;
+                    var val = this.u2p(ticks.values[i]) - shim + 'px';
+                    $(tickdivs[i]).css('top', val);
+                }
+            }
+        }    
+        
+    };
+})(jQuery);

docs/jqplot.trendLines.js

+(function($) {
+    var debug = 1;
+	// Convienence function that won't hang IE.
+	function log(something) {
+	    if (window.console && debug) {
+	        console.log(something);
+	    }
+	};
+    
+    $.jqplot.postParseOptionsHooks.push(parseTrendLineOptions);
+    $.jqplot.postDrawSeriesHooks.push(drawTrendLines);
+    $.jqplot.drawLegendHooks.push(addTrendLineLegend);
+    
+    // called witin scope of the legend object
+    // current series passed in
+    // must return null or an object {label:label, color:color}
+    function addTrendLineLegend(series) {
+        var lt = series.trendLines.label.toString();
+        var ret = (lt) ? {label:lt, color:series.trendLines.color} : null;
+        return ret;
+    }
+
+    // called within scope of ._jqPlot
+    function parseTrendLineOptions () {
+        var s, i;
+        var series = this.series;
+        var options = this.options;
+        var defaults = {
+            show: false,
+            color: '#666666',
+            lineWidth:2.5,
+            shadows:true,
+            // shadow angle in degrees
+            shadowAngle:45,
+            // shadow offset in pixels
+            shadowOffset:1,
+            shadowDepth:3,
+            shadowAlpha:'0.07',
+            label:''
+        };
+        for (i = 0; i < series.length; ++i) {
+            s = series[i];
+        	// merge user supplied trendline options with defaults.
+        	if (!s.hasOwnProperty('trendLines')) s.trendLines = {};
+        	s.trendLines = $.extend(true, {}, defaults, options.trendLines, s.trendLines);
+    	}
+    };
+    
+    // called within scope of series object
+    function drawTrendLines(grid, ctx) {
+        var fit;
+        if (this.trendLines.show) {
+            fit = fitData(this.data, 'linear');
+            // make a trendline series
+            var tl = $.extend(true, {}, this, this.trendLines);
+            tl.data = fit.data;
+            tl.gridData = [];
+            var lineRenderer = new $.jqplot.lineRenderer();
+            lineRenderer.draw.call(tl, grid, ctx); 
+        }  
+    }
+	
+	function regression(x, y, type)  {
+		var type = (type == null) ? 'linear' : type;
+	    var N = x.length;
+	    var slope;
+	    var intercept;	
+		var SX = 0;
+		var SY = 0;
+		var SXX = 0;
+		var SXY = 0;
+		var SYY = 0;
+		var Y = [];
+		var X = [];
+	
+		if (type == 'linear') {
+			X = x;
+			Y = y;
+		}
+		else if (type == 'exp' || type == 'exponential') {
+			for ( var i=0; i<y.length; i++) {
+				// ignore points <= 0, log undefined.
+				if (y[i] <= 0) {
+					N--;
+				}
+				else {
+					X.push(x[i]);
+			    	Y.push(Math.log(y[i]));
+				}
+			}
+		}
+
+		for ( var i = 0; i < N; i++) {
+		    SX = SX + X[i];
+		    SY = SY + Y[i];
+		    SXY = SXY + X[i]* Y[i];
+		    SXX = SXX + X[i]* X[i];
+		    SYY = SYY + Y[i]* Y[i];
+		}
+
+		slope = (N*SXY - SX*SY)/(N*SXX - SX*SX);
+		intercept = (SY - slope*SX)/N;
+
+	    return [slope, intercept];
+	}
+
+	function linearRegression(X,Y) {
+		var ret;
+		ret = regression(X,Y,'linear');
+		return [ret[0],ret[1]];
+	}
+
+	function expRegression(X,Y) {
+		var ret;
+		var x = X;
+		var y = Y;
+		ret = regression(x, y,'exp');
+		var base = Math.exp(ret[0]);
+		var coeff = Math.exp(ret[1]);
+		return [base, coeff];
+	}
+
+	function fitData(data, type) {
+		var type = (type == null) ?  'linear' : type;
+		var ret;
+		var res;
+		var x = [];
+		var y = [];
+		var ypred = [];
+		
+		for (i=0; i<data.length; i++){
+			if (data[i] != null && data[i][0] != null && data[i][1] != null) {
+				x.push(data[i][0]);
+				y.push(data[i][1]);
+			}
+		}
+		
+		if (type == 'linear') {
+			ret = linearRegression(x,y);
+			for ( var i=0; i<x.length; i++){
+			    res = ret[0]*x[i] + ret[1];
+			    ypred.push([x[i], res]);
+			}
+		}
+		else if (type == 'exp' || type == 'exponential') {
+			ret = expRegression(x,y);
+			for ( var i=0; i<x.length; i++){
+			    res = ret[1]*Math.pow(ret[0],x[i]);
+			    ypred.push([x[i], res]);
+			}
+		}
+		return {data: ypred, slope: ret[0], intercept: ret[1]};
+	};    
+
+})(jQuery);

docs/jquery.jqplot.js

+/* 
+Title: jqPlot
+
+JavaScript plotting library for jQuery.
+
+About: Copyright
+
+Copyright (c) 2009 Chris Leonello
+
+About: License
+
+Licensed under the MIT License.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+About: Code Conventions
+
+Classes are marked (Private) or (Public) to indicate their visibility to the user.
+Object properties and methods can, in general, be set or overriden by the options
+object passed in by the user.  However, properties prefixed with an underscore (_) 
+should be considered private and not overriden by user options.
+
+Objects and methods which extend the jQuery namespace (prefixed with a $.) are visible
+to the user through the jQuery namespace.  Except for the $.jqplot function, these are 
+primarily the renderes for lines, axes, grid, etc. which can be replaced, extended, or 
+enhanced by the user through plugins.
+
+*/
+
+(function($) {
+    var debug = 1;
+    
+    /* 
+        Class: Axis
+        (Private) An individual axis object.  Cannot be instantiated directly, but created
+        by the Plot oject.  Axis properties can be set or overriden by the 
+        options passed in from the user.
+        
+        Parameters:
+            name - Axis name (identifier).  One of 'xaxis', 'yaxis', 'x2axis' or 'y2axis'.
+    */
+    function Axis(name) {
+        // Group: Properties
+        
+        // prop: name
+        // Axis name (identifier).  One of 'xaxis', 'yaxis', 'x2axis' or 'y2axis'.
+        this.name = name;
+        // prop: show
+        // Wether to display the axis on the graph.
+        this.show = false;
+        // prop: scale
+        // Factor to multiply by the data range when setting the axis bounds
+        this.scale = 1.2;
+        // prop: numberTicks
+        // Desired number of ticks.  Computed automatically by default
+        this.numberTicks;
+        // prop: tickInterval
+        // number of units between ticks.  Mutually exclusive with numberTicks.
+        this.tickInterval;
+        // prop: renderer
+        // reference to a rendering engine that draws the axis on the plot.
+        this.renderer = new $.jqplot.lineAxisRenderer();
+        /*  
+            prop: label
+            Axis label object.  Container for axis label properties. Not implimeted yet.
+        
+            Properties:
+          
+            text - label text.
+            fontFamily - css font-family spec.
+            fontSize - css font-size spec.  
+            align - css text-align spec.
+        */
+        this.label = {text:null, fontFamily:null, fontSize:null, align:null};
+        /*  
+            prop: ticks
+            Container for axis ticks and their properties.
+        
+            Properties:
+          
+            mark - tick markings.  One of 'inside', 'outside', 'cross', '' or null.
+                The latter 2 options will hide the tick marks.
+            size - length of the tick marks in pixels.  For 'cross' style, length
+                will be stoked above and below axis, so total length will be twice this.
+            showLabels - Wether to show labels or not.
+            labels - Array of tick labels.
+            values - Array of underlying data values.
+            styles - Array of css styles to be applied.
+            formatString - formatting string passed to the tick formatter.
+            fontFamily - css font-family spec.
+            fontSize -css font-size spec.
+            textColor - css color spec.
+        */
+        this.ticks = {mark:'outside', size:4, showLabels:true, labels:[], values:[], styles:[], formatString:'%.1f', fontFamily:'', fontSize:'0.75em', textColor:''};
+        // prop: tickFormatter
+        // Function applied to format tick label text.
+        this.tickFormatter = sprintf;
+        // prop: _height
+        // height of the rendered axis in pixels.
+        this._height = 0;
+        // prop: _width
+        // width of the rendered axis in pixels.
+        this._width = 0;
+        // prop: _elem
+        // reference to the actual axis DOM element.
+        this._elem;
+        /*  
+            prop: _dataBounds
+            low/high values of all of the series bound to this axis.
+        
+            Properties:
+          
+            min - lowest value on this axis.
+            max - highest value on this axis.
+        */
+        this._dataBounds = {min:null, max:null};
+        // prop; min
+        // minimum value of the axis (in data units, not pixels).
+        this.min=null;
+        // prop: max
+        // maximum value of the axis (in data units, not pixels).
+        this.max=null;
+        // prop: style
+        // Don't know? Will have to check if this is used.
+        this.style;
+        // prop: _gridOffsets
+        // reference to the plot element grid offsets.
+        this._gridOffsets;
+        /*  
+            Property: _offsets
+            Pixel offsets from the edge of the DOM element in pixels.
+        
+            Properties:
+            min - pixel offset to the minimum value.
+            max - pixel offset to the maximum value.
+        */
+        this._offsets = {min:null, max:null};
+        // prop: _canvasWidth
+        // width of the grid canvas, total DOM element width.
+        this._canvasWidth;
+        // prop: _canvasHeight
+        // height of the grid canvas, total DOM element height.
+        this._canvasHeight;
+    };
+    
+    /* 
+        Class: Legend
+        (Private) Legend object.  Cannot be instantiated directly, but created
+        by the Plot oject.  Legend properties can be set or overriden by the 
+        options passed in from the user.
+    */
+    function Legend() {
+        // Group: Properties
+        
+        // prop: show
+        // Wether to display the legend on the graph.
+        this.show = true;
+        // prop: location
+        // Placement of the legend.  one of the compas directions: nw, n, ne, e, se, s, sw, w
+        this.location = 'se';
+        // prop: xoffset
+        // offset from the inside edge of the plot in the x direction in pixels.
+        this.xoffset = 12;
+        // prop: yoffset
+        // offset from the inside edge of the plot in the y direction in pixels.
+        this.yoffset = 12;
+        // prop: border
+        // css spec for the border around the legend box.
+        this.border = '1px solid #cccccc';
+        // prop: background
+        // css spec for the background of the legend box.
+        this.background = 'rgba(255,255,255,0.6)';
+        // prop: textColor
+        // css color spec for the legend text.
+        this.textColor = '';
+        // prop: fontFamily
+        // css font-family spec for the legend text.
+        this.fontFamily = ''; 
+        // prop: fontSize
+        // css font-size spec for the legend text.
+        this.fontSize = '0.75em';
+        // prop: rowSpacing
+        // css padding-top spec for the rows in the legend.
+        this.rowSpacing = '0.5em';
+        // prop: _elem
+        // reference to the legend DOM element.
+        this._elem;
+        
+    }
+    
+    /* 
+        Class: Title
+        (Private) Plot Title object.  Cannot be instantiated directly, but created
+        by the Plot oject.  Title properties can be set or overriden by the 
+        options passed in from the user.
+        
+        Parameters:
+            text - text of the title.
+    */
+    function Title(text) {
+        // Group: Properties
+        
+        // prop: text
+        // text of the title;
+        this.text = text;
+        // prop: fontFamily
+        // css font-family spec for the text.
+        this.fontFamily = '';
+        // prop: fontSize
+        // css font-size spec for the text.
+        this.fontSize = '1.2em';
+        // prop: textAlign
+        // css text-align spec for the text.
+        this.textAlign = 'center';
+        // prop: _elem
+        // reference to the title DOM element.
+        this._elem;
+        // prop: _height
+        // height of the DOM element in pixels.
+        this._height = 0;
+        // prop: _width
+        // width of the DOM element in pixels.
+        this._width = 0;
+        // prop: textColor
+        // css color spec for the text.
+        this.textColor = '';
+        
+    }
+    
+    /* 
+        Class: Series
+        (Private) An individual data series object.  Cannot be instantiated directly, but created
+        by the Plot oject.  Series properties can be set or overriden by the 
+        options passed in from the user.
+    */
+    function Series() {
+        // Group: Properties
+        
+        // prop: show
+        // wether or not to draw the series.
+        this.show = true;
+        // prop: xaxis
+        // name of x axis to associate with this series.
+        this.xaxis = 'xaxis';
+        // prop: _xaxis
+        // reference to the underlying x axis object associated with this series.
+        this._xaxis = new Axis(this.xaxis);
+        // prop: yaxis
+        // name of y axis to associate with this series.
+        this.yaxis = 'yaxis';
+        // prop: _yaxis
+        // reference to the underlying y axis object associated with this series.
+        this._yaxis = new Axis(this.yaxis);
+        // prop: renderer
+        // reference to a renderer which will actually draw this series.
+        this.renderer = new $.jqplot.lineRenderer();
+        // prop: data
+        // raw user data points.  These should never be altered!!!
+        this.data = [];
+        // prop: gridData
+        // data in grid coordinates.  User data transformed for plotting on grid.
+        this.gridData = [];
+        // place holder, don't do anything with points yet.
+        //this.points = {show:true, renderer: 'circleRenderer'};
+        // prop: color
+        // css color spec for the series
+        this.color;
+        // prop: lineWidth
+        // width of the line in pixels.  May have different meanings depending on renderer.
+        this.lineWidth = 2.5;
+        // prop: shadow
+        // wether or not to draw a shadow on the line
+        this.shadow = true;
+        // prop: shadowAngle
+        // Shadow angle in degrees
+        this.shadowAngle = 45;
+        // prop: shadowOffset
+        // Shadow offset from line in pixels
+        this.shadowOffset = 1;
+        // prop: shadowDepth
+        // Number of times shadow is stroked, each stroke offset shadowOffset from the last.
+        this.shadowDepth = 3;
+        // prop: shadowAlpha
+        // Alpha channel transparency of shadow.  0 = transparent.
+        this.shadowAlpha = '0.07';
+        // prop: breakOnNull
+        // wether line segments should be be broken at null value.
+        // False will join point on either side of line.
+        this.breakOnNull = false;
+        // prop: label
+        // Line label to use in legend.
+        this.label = '';
+        // prop: marks
+        // Options for data point markers
+    };
+    
+    /* 
+        Class: Grid
+        (Private) Object representing the grid on which the plot is drawn.  The grid in this
+        context is the area bounded by the axes, the area which will contain the series.
+        The Grid object cannot be instantiated directly, but is created by the Plot oject.  
+        Grid properties can be set or overriden by the options passed in from the user.
+    */
+    function Grid() {
+        // Group: Properties
+        
+        // prop: drawGridlines
+        // wether to draw the gridlines on the plot.
+        this.drawGridlines = true;
+        // prop: background
+        // css spec for the background color.
+        this.background = '#fffdf6';
+        // prop: borderColor
+        // css spec for the color of the grid border.
+        this.borderColor = '#999999';
+        // prop: borderWidth
+        // width of the border in pixels.
+        this.borderWidth = 2.0;
+        // prop: shadow
+        // wether to show a shadow behind the grid.
+        this.shadow = true;
+        // prop: shadowAngle
+        // shadow angle in degrees
+        this.shadowAngle = 45;
+        // prop: shadowOffset
+        // Offset of each shadow stroke from the border in pixels
+        this.shadowOffset = 1.5;
+        // prop: shadowWidth
+        // width of the stoke for the shadow
+        this.shadowWidth = 3;
+        // prop: shadowDepth
+        // Number of times shadow is stroked, each stroke offset shadowOffset from the last.
+        this.shadowDepth = 3;
+        // prop: shadowAlpha
+        // Alpha channel transparency of shadow.  0 = transparent.
+        this.shadowAlpha = '0.07';
+        // prop: _width
+        // width of the grid area bounded by the border.
+        this._width;
+        // prop: _height
+        // height of the grid area bounded by the border.
+        this._height;
+        // prop: _top
+        // position of the top of the grid measures from the top left of the DOM container.
+        this._top;
+        // prop: _bottom
+        // position of the bottom of the grid measures from the top left of the DOM container.
+        this._bottom;
+        // prop: _left
+        // position of the left of the grid measures from the top left of the DOM container.
+        this._left;
+        // prop: _right
+        // position of the right of the grid measures from the top left of the DOM container.
+        this._right;
+        // prop: renderer
+        // reference to the object which will actually render the grid.
+        this.renderer = new $.jqplot.canvasGridRenderer();
+        
+    };
+
+    
+    /* 
+        Class: jqPlot
+        (Private) Plot object returned to call to $.jqplot.  Handles parsing user options,
+        creating sub objects (Axes, legend, title, series) and rendering the plot.
+    */    
+    function jqPlot() {
+        // user's data.  Should be in the form of
+        // [ [[x1, y1], [x2, y2],...], [[x1, y1], [x2, y2], ...] ] or
+        // [{ data:[[x1, y1], [x2, y2],...], other_options...}, { data:[[x1, y1], [x2, y2],...], other_options...} ]
+        this.data = [];
+        // the id of the dom element to render the plot into
+        this.targetId = null;
+        // the jquery object for the dom target.
+        this.target = null;    
+        // default options object.
+        // Fill in axes properties by default so don't throw an error.
+        // Remind me that can set defaults for all points, axes, series at once.
+        this.defaults = {
+            pointsDefaults: {},
+            axesDefaults: {},
+            axes: {xaxis:{}, yaxis:[], x2axis:{}, y2axis:{}},
+            seriesDefaults: {},
+            series:[]
+        };
+        // container for the individual data series
+        this.series = [];
+        // up to 4 axes are supported, each with it's own options.
+        this.axes = {xaxis: new Axis('xaxis'), yaxis: new Axis('yaxis'), x2axis: new Axis('x2axis'), y2axis: new Axis('y2axis')};
+        this.grid = new Grid();
+        this.legend = new Legend();
+        // this.title = {text:null, font:null};
+        // handle to the grid canvas drawing context.  Holds the axes, grid, and labels.
+        // Stuff that should be rendered only at initial plot drawing.
+        this.gctx = null;
+        // handle to the series  canvas drawing context.  Holds the rendered
+        // rendered series which may be manipulated through user interaction.
+        this.sctx = null;
+        // handle to the overlay canvas drawing object.  Holds interactive content
+        // like highlights that are rendered according to user interaction
+        this.octx = null;
+        // width and height of the canvas
+        this._width = null;
+        this._height = null; 
+        this._gridOffsets = {top:10, right:10, bottom:10, left:10};
+        this.equalXTicks = true;
+        this.equalYTicks = true;
+        // borrowed colors from Flot.
+        this.seriesColors = ["#edc240", "#afd8f8", "#cb4b4b", "#4da74d", "#9440ed"];
+        // Default font characteristics which can be overriden by individual 
+        // plot elements.  All are css specs.
+        this.textColor = '#666666';
+        this.fontFamily = 'Trebuchet MS, Arial, Helvetica, sans-serif';
+        this.fontSize = '1em';
+        this.title = new Title();
+        // container to hold all of the merged options.  Convienence for plugins.
+        this.options = {};
+        
+        // Constructor: init
+        // Initializes the jqPlot object, parsing the user options and processing the data.
+        //
+        // Parameter:
+        // target - ID of the DOM element the plot will render into.
+        // data - data series.
+        // options - user specified options object.    
+        this.init = function(target, data, options) {
+            this.targetId = target;
+            this.target = $('#'+target);
+            // make sure the target is positioned by some means
+            if (!this.target) throw "No plot target specified";
+            if (this.target.css('position') == 'static') this.target.css('position', 'relative');
+            this.target.css('color', this.textColor);
+            this.target.css('font-family', this.fontFamily);
+            this.target.css('font-size', this.fontSize);
+            this._height = parseFloat(this.target.css('height'));
+            this._width = parseFloat(this.target.css('width'));
+            if (this._height <=0 || this._width <=0) throw "Canvas dimensions <=0";
+            // get a handle to the plot object from the target to help with events.
+            $(target).data('jqplot', this);
+            this.data = data;
+            if (data.length < 1 || data[0].length < 2) throw "Invalid Plot data";
+            this.parseOptions(options);
+            
+            // set the dataBounds (min and max) for each axis
+            for (var i=0; i<this.series.length; i++) {
+                this.series[i].renderer.processData.call(this.series[i]);
+            }
+        }
+    
+        // Function: parseOptions
+        //  Parses the user's options overriding defaults.
+        //
+        // Parameters:
+        // options - options object passed into $.jqplot by user.
+        this.parseOptions = function(options){
+            this.options = $.extend(true, {}, this.defaults, options);
+            for (var n in this.axes) {
+                var axis = this.axes[n];
+                $.extend(true, axis, this.options.axesDefaults, this.options.axes[n]);
+                switch (n) {
+                    case 'xaxis':
+                        //axis.style = {position:'absolute', left:'0px', bottom:'0px'};
+                        axis._width = this._width;
+                        axis.gridOffset = 'bottom';
+                        break;
+                    case 'x2axis':
+                        //axis.style = {position:'absolute', left:'0px', top:'0px'};
+                        axis._width = this._width;
+                        axis.gridOffset = 'top';
+                        break;
+                    case 'yaxis':
+                        //axis.style = {position:'absolute', left:'0px', top:'0px'};
+                        axis._height = this._height;
+                        axis.gridOffset = 'left';
+                        break;
+                    case 'y2axis':
+                        //axis.style = {position:'absolute', right:'0px', top:'0px'};
+                        axis._height = this._height;
+                        axis.gridOffset = 'right';
+                        break;
+                    default:
+                        break;
+                }
+            }
+            
+            for (var i=0; i<this.data.length; i++) {
+                var temp = $.extend(true, new Series(), this.seriesDefaults, this.options.series[i]);
+                temp.data = this.data[i];
+                switch (temp.xaxis) {
+                    case 'xaxis':
+                        temp._xaxis = this.axes.xaxis;
+                        break;
+                    case 'x2axis':
+                        temp._xaxis = this.axes.x2axis;
+                        break;
+                    default:
+                        break;
+                }
+                switch (temp.yaxis) {
+                    case 'yaxis':
+                        temp._yaxis = this.axes.yaxis;
+                        break;
+                    case 'y2axis':
+                        temp._yaxis = this.axes.y2axis;
+                        break;
+                    default:
+                        break;
+                }
+                if (temp.show) {
+                    temp._xaxis.show = true;
+                    temp._yaxis.show = true;
+                }
+                if (!temp.color) temp.color = this.seriesColors[i];
+                this.series.push(temp);
+            }
+            
+            // copy the grid and title options into this object.
+            $.extend(true, this.grid, this.options.grid);
+            if (typeof this.options.title == 'string') this.title.text = this.options.title;
+            else if (typeof this.options.title == 'object') $.extend(true, this.title, this.options.title);
+            $.extend(true, this.legend, this.options.legend);
+            
+            for (var i=0; i<$.jqplot.postParseOptionsHooks.length; i++) {
+                $.jqplot.postParseOptionsHooks[i].call(this);
+            }
+        };
+    
+        // Function: draw
+        // Calls functions needed to draw the plot.
+        this.draw = function(){
+            this.drawTitle();
+            this.drawAxes();
+            this.pack();
+            this.grid.renderer.createDrawingContext.call(this);
+            this.grid.renderer.draw.call(this.grid, this.gctx, this.axes);
+            this.drawLegend();
+            this.drawSeries();
+            for (var i=0; i<$.jqplot.postDrawHooks.length; i++) {
+                $.jqplot.postDrawHooks[i].call(this);
+            }
+        };
+        
+        // Function: drawTitle
+        // Draws the plot title
+        this.drawTitle = function(){
+            // title will alway start at the top left
+            var t = this.title;
+            if (t.text) {
+                var styletext = 'padding-bottom:0.4em;text-align:center;'+
+                    'position:absolute;top:0px;left:0px;width:'+this._width+'px;';
+                styletext += (t.textColor) ? 'color:'+t.textColor+';' : '';
+                t._elem = $('<div class="jqplot-title" style="'+styletext+'">'+t.text+'</div>').appendTo(this.target);
+                t._height = $(t._elem).outerHeight(true);
+                t._width = $(t._elem).outerWidth(true);              
+            }
+        };
+        
+        // Function: drawAxes
+        // Draws the axes on the plot.
+        this.drawAxes = function(){
+            for (var name in this.axes) {
+                this.axes[name].renderer.draw.call(this.axes[name], this.target, this._height, this._width);
+            }
+        };
+        
+        // Function: pack
+        // Dimensions an positions the grid and axes.
+        this.pack = function() {
+            // calculate grid offsets
+            var offsets = this._gridOffsets;
+            var axes = this.axes;
+            var temp
+            temp = this.title._height + axes.x2axis._height;
+            if (temp) offsets.top = temp;
+            if (axes.yaxis._width) offsets.left = axes.yaxis._width;
+            if (axes.xaxis._height) offsets.bottom = axes.xaxis._height;
+            if (axes.y2axis._width) offsets.right = axes.y2axis._width;
+            
+            this.grid._top = this._gridOffsets.top;
+            this.grid._left = this._gridOffsets.left;
+            this.grid._height = this._height - this._gridOffsets.top - this._gridOffsets.bottom;
+            this.grid._width = this._width - this._gridOffsets.left - this._gridOffsets.right;
+            this.grid._bottom = this.grid._top + this.grid._height;
+            this.grid._right = this.grid._left + this.grid._width;
+            
+            for (var name in this.axes) {
+                var axis = this.axes[name];
+                axis.renderer.pack.call(axis, offsets, this.grid._width, this.grid._height);
+            }
+            
+        };
+        
+        // Function: drawLegend
+        // Draws the legend on top of the grid.  Renders it as a table.        
+        this.drawLegend = function() {
+            var legend = this.legend;
+            var grid = this.grid;
+            if (legend.show) {
+                var series = this.series;
+                // make a table.  one line label per row.
+                var ss = 'background:'+legend.background+';border:'+legend.border+';position:absolute;';
+                ss += (legend.fontSize) ? 'font-size:'+legend.fontSize+';' : '';
+                ss += (legend.fontFamily) ? 'font-family:'+legend.fontFamily+';' : '';
+                ss += (legend.textColor) ? 'color:'+legend.textColor+';' : '';
+                switch (legend.location) {
+                    case 'nw':
+                        var a = grid._left + legend.xoffset;
+                        var b = grid._top + legend.yoffset;
+                        ss += 'left:'+a+'px;top:'+b+'px;';
+                        break;
+                    case 'n':
+                        var a = (grid._left + grid._right)/2 + legend.xoffset;
+                        var b = grid._top + legend.yoffset;
+                        ss += 'left:'+a+'px;top:'+b+'px;';
+                        break;
+                    case 'ne':
+                        var a = grid._right - legend.xoffset;
+                        var b = grid._top + legend.yoffset;
+                        ss += 'right:'+a+'px;top:'+b+'px;';
+                        break;
+                    case 'e':
+                        var a = grid._right - legend.xoffset;
+                        var b = (grid._top + grid._bottom)/2 + legend.yoffset;
+                        ss += 'right:'+a+'px;top:'+b+'px;';
+                        break;
+                    case 'se':
+                        var a = this._width - grid._right + legend.xoffset;
+                        var b = this._height - grid._bottom + legend.yoffset;
+                        ss += 'right:'+a+'px;bottom:'+b+'px;';
+                        break;
+                    case 's':
+                        var a = (grid._left + grid._right)/2 + legend.xoffset;
+                        var b = grid._bottom + legend.yoffset;
+                        ss += 'left:'+a+'px;bottom:'+b+'px;';
+                        break;
+                    case 'sw':
+                        var a = grid._left + legend.xoffset;
+                        var b = grid._bottom + legend.yoffset;
+                        ss += 'left:'+a+'px;bottom:'+b+'px;';
+                        break;
+                    case 'w':
+                        var a = grid._left + legend.xoffset;
+                        var b = (grid._top + grid._bottom)/2 + legend.yoffset;
+                        ss += 'left:'+a+'px;top:'+b+'px;';
+                        break;
+                    default:  // same as 'se'
+                        var a = grid._right - legend.xoffset;
+                        var b = grid._bottom + legend.yoffset;
+                        ss += 'right:'+a+'px;bottom:'+b+'px;';
+                        break;
+                        
+                }
+                legend._elem = $('<table class="jqplot-legend" style="'+ss+'"></table>').appendTo(this.target).get(0);
+                
+                function addrow(label, color, pad) {
+                    var rs = (pad) ? legend.rowSpacing : '0';
+                    var tr = $('<tr class="jqplot-legend"></tr>').appendTo(legend._elem);
+                    $('<td class="jqplot-legend" style="vertical-align:middle;text-align:center;padding-top:'+rs+';">'+
+                        '<div style="border:1px solid #cccccc;padding:0.2em;">'+
+                        '<div style="width:1.2em;height:0.7em;background-color:'+color+';"></div>'+
+                        '</div></td>').appendTo(tr);
+                    $('<td class="jqplot-legend" style="vertical-align:middle;padding-top:'+rs+';">'+label+'</td>').appendTo(tr);
+                };
+                
+                var pad = false;
+                for (var i = 0; i< series.length; i++) {
+                    s = series[i];
+                    var lt = s.label.toString();
+                    if (lt) {
+                        addrow(lt, s.color, pad);
+                        pad = true;
+                    }
+                    for (var j=0; j<$.jqplot.drawLegendHooks.length; j++) {
+                        var item = $.jqplot.drawLegendHooks[j].call(legend, s);
+                        if (item) {
+                            addrow(item.label, item.color, pad);
+                            pad = true;
+                        } 
+                    }
+                }
+            }
+        };
+
+        // Function: drawSeries
+        // Calls the series renderer for each series in the plot within the context
+        // of the individual series.
+        this.drawSeries = function(){
+            for (var i=0; i<this.series.length; i++) {
+                this.series[i].renderer.draw.call(this.series[i], this.grid, this.sctx);
+                for (var j=0; j<$.jqplot.postDrawSeriesHooks.length; j++) {
+                    $.jqplot.postDrawSeriesHooks[j].call(this.series[i], this.grid, this.sctx);
+                }
+            }
+        };
+    };
+    
+    // Class: $.jqplot
+    // (Public) jQuery extension called by user to create plot.
+    //
+    // Parameters:
+    // target - ID of target element to render the plot into.
+    // data - an array of data series.
+    // options - user defined options object.
+    $.jqplot = function(target, data, options) {
+        options = options || {};
+        var plot = new jqPlot();
+        plot.init(target, data, options);
+        plot.draw();
+        return plot;
+    };
+    
+    // array: $.jqplot.postParseOptionsHooks
+    // Array of plugin hooks run after jqPlot.parseOptions method
+    $.jqplot.postParseOptionsHooks = [];
+    // array: $.jqplot.postDrawHooks
+    // Array of plugin hooks run after jqPlot.draw method
+    $.jqplot.postDrawHooks = [];
+    // array: $.jqplot.postDrawSeriesHooks
+    // Array of plugin hooks run after each series renderer's draw method is called in jqPlot.drawSeries method.
+    $.jqplot.postDrawSeriesHooks = [];
+    // array: $.jqplot.drawLegendHooks
+    // Array of plugin hooks run within but at the end of the jqPlot.drawLegend method.
+    $.jqplot.drawLegendHooks = [];
+           
+    $.jqplot.lineAxisRenderer = function() {
+    };
+    
+    
+    // called with scope of axis
+    $.jqplot.lineAxisRenderer.prototype.draw = function(target, plotHeight, plotWidth) {
+        var axis = this;
+        if (axis.show) {
+            // populate the axis label and value properties.
+            axis.renderer.setAxis.call(axis, plotHeight, plotWidth);
+            // fill a div with axes labels in the right direction.
+            // Need to pregenerate each axis to get it's bounds and
+            // position it and the labels correctly on the plot.
+            var h, w;
+            
+            axis._elem = $('<div class="jqplot-axis"></div>').appendTo(target).get(0);
+            //for (var s in axis.style) $(axis._elem).css(s, axis.style[s]);
+    
+            if (axis.ticks.showLabels) {
+                for (var i=0; i<axis.ticks.labels.length; i++) {
+                    var elem = $('<div class="jqplot-axis-tick"></div>').appendTo(axis._elem).get(0);
+                    
+                    for (var s in axis.ticks.styles[i]) $(elem).css(s, axis.ticks.styles[i][s]);
+                    $(elem).html(axis.ticks.labels[i]);
+                    
+                    if (axis.ticks.fontFamily) elem.style.fontFamily = axis.ticks.fontFamily;
+                    if (axis.ticks.fontSize) elem.style.fontSize = axis.ticks.fontSize;
+                    
+                    h = $(elem).outerHeight(true);
+                    w = $(elem).outerWidth(true);
+                    
+                    if (axis._height < h) {
+                        axis._height = h;
+                    }
+                    if (axis._width < w) {
+                        axis._width = w;
+                    }
+                }
+            }
+        }
+    };
+    
+    // called with scope of an axis
+    // Populate the axis properties, giving a label and value
+    // (corresponding to the user data coordinates, not plot coords.)
+    // for each tick on the axis.
+    $.jqplot.lineAxisRenderer.prototype.setAxis = function(plotHeight, plotWidth) {
+        // if a ticks array is specified, use it to fill in
+        // the labels and values.
+        var axis = this;
+        axis._canvasHeight = plotHeight;
+        axis._canvasWidth = plotWidth;
+        var db = axis._dataBounds;
+        if (axis.ticks && axis.ticks.constructor == Array) {
+            var temp = $.extend(true, [], axis.ticks);
+            // if 2-D array, match them up
+            if (temp[0].lenth) {
+                for (var i=0; i< temp; i++) {
+                    axis.ticks.labels.push(temp[i][1].toString());
+                    axis.ticks.values.push(parseFloat(temp[i][0]));
+                }
+            }
+            // else 1-D array
+            else {
+                for (var i=0; i< temp; i++) {
+                    axis.ticks.labels.push(temp[i].toString());
+                    axis.ticks.values.push(parseFloat(temp[i]));
+                }
+            }
+        }
+        // else call the axis renderer and fill in the labels
+        // and values from there.
+        else axis.renderer.fill.call(axis);
+    };
+    
+    $.jqplot.lineAxisRenderer.prototype.fill = function() {
+        var name = this.name;
+        var db = this._dataBounds;
+        var dim, interval;
+        var min, max;
+        var pos1, pos2;
+        if (name == 'xaxis' || name == 'x2axis') {
+            dim = this._canvasWidth;
+        }
+        else {
+            dim = this._canvasHeight;
+        }
+        if (this.numberTicks == null){
+            if (dim > 100) {
+                this.numberTicks = parseInt(3+(dim-100)/75);
+            }
+            else this.numberTicks = 3;
+        }
+        
+        
+        min = ((this.min != null) ? this.min : db.min);
+        max = ((this.max != null) ? this.max : db.max);
+        var range = max - min;
+        var rmin = min - (this.min != null ? 0 : range/2*(this.scale - 1));
+        var rmax = max + (this.max != null ? 0 : range/2*(this.scale - 1));
+        this.min = rmin;
+        this.max = rmax;
+        this.tickInterval = (rmax - rmin)/(this.numberTicks-1);
+        for (var i=0; i<this.numberTicks; i++){
+            var tt = rmin + i*this.tickInterval
+            this.ticks.labels.push(this.tickFormatter(this.ticks.formatString, tt));
+            this.ticks.values.push(rmin + i*this.tickInterval);
+            var pox = i*15+'px';
+            switch (name) {
+                case 'xaxis':
+                    this.ticks.styles.push({position:'absolute', top:'0px', left:pox, paddingTop:'10px'});
+                    break;
+                case 'x2axis':
+                    this.ticks.styles.push({position:'absolute', bottom:'0px', left:pox, paddingBottom:'10px'});
+                    break;
+                case 'yaxis':
+                    this.ticks.styles.push({position:'absolute', right:'0px', top:pox, paddingRight:'10px'});
+                    break;
+                case 'y2axis':
+                    this.ticks.styles.push({position:'absolute', left:'0px', top:pox, paddingLeft:'10px'});
+                    break;
+                    
+            }
+        }
+        if (name == 'yaxis' || name == 'y2axis') this.ticks.styles.reverse();
+    };
+    
+    // Now we know offsets around the grid, we can define conversioning functions
+    // and properly lay out the axes.
+    $.jqplot.lineAxisRenderer.prototype.pack = function(offsets, gridwidth, gridheight) {
+        var ticks = this.ticks;
+        var tickdivs = $(this._elem).children('div');
+        if (this.name == 'xaxis' || this.name == 'x2axis') {
+            this._offsets = {min:offsets.left, max:offsets.right};
+            
+            this.p2u = function(p) {
+                return (p - this._offsets.min)*(this.max - this.min)/(this._canvasWidth - this._offsets.max - this._offsets.min) + this.min;
+            }
+            
+            this.u2p = function(u) {
+                return (u - this.min) * (this._canvasWidth - this._offsets.max - this._offsets.min) / (this.max - this.min) + this._offsets.min;
+            }
+            
+            this.series_u2p = function(u) {
+                return (u - this.min) * gridwidth / (this.max - this.min);
+            }
+            
+            this.series_p2u = function(p) {
+                return p * (this.max - this.min) / gridwidth + this.min;
+            }
+            
+            if (this.show) {
+                // set the position
+                if (this.name == 'xaxis') {
+                    $(this._elem).css({position:'absolute', left:'0px', top:(this._canvasHeight-offsets.bottom)+'px'});
+                }
+                else {
+                    $(this._elem).css({position:'absolute', left:'0px', bottom:(this._canvasHeight-offsets.top)+'px'});
+                }
+                for (i=0; i<tickdivs.length; i++) {
+                    var shim = $(tickdivs[i]).outerWidth(true)/2;
+                    //var t = this.u2p(ticks.values[i]);
+                    var val = this.u2p(ticks.values[i]) - shim + 'px';
+                    $(tickdivs[i]).css('left', val);
+                    // remember, could have done it this way
+                    //tickdivs[i].style.left = val;
+                }
+            }
+        }  
+        else {
+            this._offsets = {min:offsets.bottom, max:offsets.top};
+            
+            this.p2u = function(p) {
+                return (p - this._canvasHeight + this._offsets.min)*(this.max - this.min)/(this._canvasHeight - this._offsets.min - this._offsets.max) + this.min;
+            }
+            
+            this.u2p = function(u) {
+                return -(u - this.min) * (this._canvasHeight - this._offsets.min - this._offsets.max) / (this.max - this.min) + this._canvasHeight - this._offsets.min;
+            }
+            
+            this.series_u2p = function(u) {
+                return (this.max - u) * gridheight /(this.max - this.min);
+            }
+            
+            this.series_p2u = function(p) {
+                return -p * (this.max - this.min) / gridheight + this.max;
+            }
+            
+            if (this.show) {
+                // set the position
+                if (this.name == 'yaxis') {
+                    $(this._elem).css({position:'absolute', right:(this._canvasWidth-offsets.left)+'px', top:'0px'});
+                }
+                else {
+                    $(this._elem).css({position:'absolute', left:(this._canvasWidth - offsets.right)+'px', top:'0px'});
+                }
+                for (i=0; i<tickdivs.length; i++) {
+                    var shim = $(tickdivs[i]).outerHeight(true)/2;
+                    var val = this.u2p(ticks.values[i]) - shim + 'px';
+                    $(tickdivs[i]).css('top', val);
+                }
+            }
+        }    
+        
+    };
+    
+    // Class: $.jqplot.canvasGridRenderer
+    // (Public) Rendrer for the jqPlot grid which draws the grid as a canvas element on the page.
+    $.jqplot.canvasGridRenderer = function(){};
+    
+    // Function: createDrawingContext
+    // (Public) Creates (but doesn't populate) the actual canvas elements for plotting.
+    // Called within context of jqPlot object.
+    $.jqplot.canvasGridRenderer.prototype.createDrawingContext = function(){
+        this.gridCanvas = document.createElement('canvas');
+        this.gridCanvas.width = this._width;
+        this.gridCanvas.height = this._height;
+        if ($.browser.msie) // excanvas hack
+            this.gridCanvas = window.G_vmlCanvasManager.initElement(this.gridCanvas);
+        $(this.gridCanvas).css({ position: 'absolute', left: 0, top: 0 });
+        this.target.append(this.gridCanvas);
+        this.gctx = this.gridCanvas.getContext("2d");
+        
+        this.seriesCanvas = document.createElement('canvas');
+        this.seriesCanvas.width = this.grid._width;
+        this.seriesCanvas.height = this.grid._height;
+        if ($.browser.msie) // excanvas hack
+            this.seriesCanvas = window.G_vmlCanvasManager.initElement(this.seriesCanvas);
+        $(this.seriesCanvas).css({ position: 'absolute', left: this.grid._left, top: this.grid._top });
+        this.target.append(this.seriesCanvas);
+        this.sctx = this.seriesCanvas.getContext("2d");
+        
+        this.overlayCanvas = document.createElement('canvas');
+        this.overlayCanvas.width = this._width;
+        this.overlayCanvas.height = this._height;
+        if ($.browser.msie) // excanvas hack
+            this.overlayCanvas = window.G_vmlCanvasManager.initElement(this.overlayCanvas);
+        $(this.overlayCanvas).css({ position: 'absolute', left: 0, top: 0 });
+        this.target.append(this.overlayCanvas);
+        this.octx = this.overlayCanvas.getContext("2d");
+    };
+    
+    $.jqplot.canvasGridRenderer.prototype.draw = function(ctx, axes) {
+        var grid = this;
+        // Add the grid onto the grid canvas.  This is the bottom most layer.
+        ctx.save();
+        ctx.fillStyle = grid.background;
+        ctx.fillRect(grid._left, grid._top, grid._width, grid._height);
+        if (grid.drawGridlines) {
+            ctx.save();
+            ctx.lineJoin = 'miter';
+            ctx.lineCap = 'round';
+            ctx.lineWidth = 1;
+            ctx.strokeStyle = '#cccccc';
+            for (var name in axes) {
+                var axis = axes[name];
+                if (axis.show) {
+                    var ticks = axis.ticks;
+                    switch (name) {
+                        case 'xaxis':
+                            for (var i=0; i<ticks.values.length; i++) {
+                                var pos = Math.round(axis.u2p(ticks.values[i])) + 0.5;
+                                ctx.beginPath();
+                                ctx.moveTo(pos, grid._top);
+                                ctx.lineTo(pos, grid._bottom);
+                                ctx.stroke();
+                            }
+                            break;
+                        case 'yaxis':
+                            for (var i=0; i<ticks.values.length; i++) {
+                                var pos = Math.round(axis.u2p(ticks.values[i])) + 0.5;
+                                ctx.beginPath();
+                                ctx.moveTo(grid._right, pos);
+                                ctx.lineTo(grid._left, pos);
+                                ctx.stroke();
+                            }
+                            break;
+                        case 'x2axis':
+                            for (var i=0; i<ticks.values.length; i++) {
+                                var pos = Math.round(axis.u2p(ticks.values[i])) + 0.5;
+                                ctx.beginPath();
+                                ctx.moveTo(pos, grid._bottom);
+                                ctx.lineTo(pos, grid._top);
+                                ctx.stroke();
+                            }
+                            break;
+                        case 'y2axis':
+                            for (var i=0; i<ticks.values.length; i++) {
+                                var pos = Math.round(axis.u2p(ticks.values[i])) + 0.5;
+                                ctx.beginPath();
+                                ctx.moveTo(grid._left, pos);
+                                ctx.lineTo(grid._right, pos);
+                                ctx.stroke();
+                            }
+                            break;
+                    }
+                }
+            }
+            ctx.restore();
+        }
+        
+        function drawMark(bx, by, ex, ey) {
+            ctx.beginPath();